Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
=============================
Sale Automatic Workflow Stock
=============================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:e90599c17ac75eede3497047126b77de1fd298dd20a818f82e154a1af06230d4
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/sale-workflow/tree/18.0/sale_automatic_workflow_stock
:alt: OCA/sale-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/sale-workflow-18-0/sale-workflow-18-0-sale_automatic_workflow_stock
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=18.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
Create stock related workflows with more or less automatization and
apply it on sales orders.
Workflows are extended with:
- Apply default values:
- Packing Policy (partial, complete)
- Apply automatic actions:
- Confirm the picking
**Table of contents**
.. contents::
:local:
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-workflow/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_automatic_workflow_stock%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Akretion
* Camptocamp
* Sodexis
Contributors
------------
- Guewen Baconnier
- Beau Sebastien
- Leonardo Pistone
- Stéphane Bidoul
- Damien Crier
- Alexandre Fayolle
- Sodexis
- Dave Lasley <dave@laslabs.com>
- Akim Juillerat <akim.juillerat@camptocamp.com>
- Thomas Fossoul <thomas@niboo.com>
- Silvija Butko <silvija@focusate.eu>
- Tri Doan <<tridm@trobz.com>>
- Chau Le <<chaulb@trobz.com>>
Other credits
-------------
The migration of this module from 17.0 to 18.0 was financially supported
by Camptocamp.
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
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.
This module is part of the `OCA/sale-workflow <https://github.com/OCA/sale-workflow/tree/18.0/sale_automatic_workflow_stock>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1 @@
from . import models

View File

@@ -0,0 +1,23 @@
# Copyright 2011 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
# Copyright 2013 Camptocamp SA (author: Guewen Baconnier)
# Copyright 2016 Sodexis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Sale Automatic Workflow Stock",
"version": "18.0.1.0.0",
"category": "Sales Management",
"license": "AGPL-3",
"author": "Akretion, "
"Camptocamp, "
"Sodexis, "
"Odoo Community Association (OCA)",
"website": "https://github.com/OCA/sale-workflow",
"depends": ["sale_automatic_workflow", "sale_stock"],
"data": [
"views/sale_order_views.xml",
"views/sale_workflow_process_views.xml",
"data/automatic_workflow_data.xml",
],
"auto_install": True,
}

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright (C) 2011 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo noupdate="1">
<!-- Filters -->
<record id="automatic_workflow_picking_filter" model="ir.filters">
<field name="name">Automatic Workflow Picking Filter</field>
<field name="model_id">stock.picking</field>
<field
name="domain"
>[('state', 'in', ['draft', 'confirmed', 'assigned'])]</field>
<field name="user_id" ref="base.user_root" />
</record>
<!-- Processes -->
<record
id="sale_automatic_workflow.automatic_validation"
model="sale.workflow.process"
>
<field name="picking_policy">one</field>
<field name="validate_picking" eval="0" />
<field name="picking_filter_id" eval="automatic_workflow_picking_filter" />
</record>
<record
id="sale_automatic_workflow.manual_validation"
model="sale.workflow.process"
>
<field name="picking_policy">one</field>
<field name="validate_picking" eval="0" />
<field name="picking_filter_id" eval="automatic_workflow_picking_filter" />
</record>
</odoo>

View File

@@ -0,0 +1,98 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_automatic_workflow_stock
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-07-09 09:58+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.6.2\n"
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields,field_description:sale_automatic_workflow_stock.field_sale_workflow_process__validate_picking
msgid "Confirm and Transfer Picking"
msgstr "Conferma e trasferisci il prelievo"
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields.selection,name:sale_automatic_workflow_stock.selection__sale_workflow_process__picking_policy__one
msgid "Deliver all products at once"
msgstr "Consegna tutti i prodotti in una volta"
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields.selection,name:sale_automatic_workflow_stock.selection__sale_workflow_process__picking_policy__direct
msgid "Deliver each product when available"
msgstr "Consegna ogni prodotto quando disponibile"
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields,help:sale_automatic_workflow_stock.field_sale_order__picking_policy
msgid ""
"If you deliver all products at once, the delivery order will be scheduled "
"based on the greatest product lead time. Otherwise, it will be based on the "
"shortest."
msgstr ""
"Se tutti i prodotti vengono consegnati insieme, l'ordine di consegna verrà "
"pianificato in base al tempo di attraversamento maggiore. Altrimenti, verrà "
"utilizzato il più corto."
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields,field_description:sale_automatic_workflow_stock.field_sale_workflow_process__picking_filter_id
msgid "Picking Filter"
msgstr "Filtro prelIevo"
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields,field_description:sale_automatic_workflow_stock.field_sale_workflow_process__picking_filter_domain
msgid "Picking Filter Domain"
msgstr "Dominio filtro prelievo"
#. module: sale_automatic_workflow_stock
#: model:ir.model,name:sale_automatic_workflow_stock.model_sale_workflow_process
#: model:ir.model.fields,field_description:sale_automatic_workflow_stock.field_stock_picking__workflow_process_id
msgid "Sale Workflow Process"
msgstr "Flusso di vendita"
#. module: sale_automatic_workflow_stock
#: model:ir.model,name:sale_automatic_workflow_stock.model_sale_order
msgid "Sales Order"
msgstr "Ordine di vendita"
#. module: sale_automatic_workflow_stock
#: model:ir.model,name:sale_automatic_workflow_stock.model_automatic_workflow_job
msgid ""
"Scheduler that will play automatically the validation of invoices, "
"pickings..."
msgstr ""
"Schedulatore che riprodurrà in automatico la validazione delle fatture, "
"prelievi..."
#. module: sale_automatic_workflow_stock
#: model_terms:ir.ui.view,arch_db:sale_automatic_workflow_stock.sale_workflow_process_view_form_inherit
msgid ""
"Set selection based on a search\n"
" filter:"
msgstr ""
"Imposta seleziona in base ad un filtro\n"
" ricerca:"
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields,field_description:sale_automatic_workflow_stock.field_sale_order__picking_policy
#: model:ir.model.fields,field_description:sale_automatic_workflow_stock.field_sale_workflow_process__picking_policy
msgid "Shipping Policy"
msgstr "Politica di spedizione"
#. module: sale_automatic_workflow_stock
#: model:ir.model,name:sale_automatic_workflow_stock.model_stock_move
msgid "Stock Move"
msgstr "Movimento di magazzino"
#. module: sale_automatic_workflow_stock
#: model:ir.model,name:sale_automatic_workflow_stock.model_stock_picking
msgid "Transfer"
msgstr "Trasferimento"

View File

@@ -0,0 +1,88 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_automatic_workflow_stock
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields,field_description:sale_automatic_workflow_stock.field_sale_workflow_process__validate_picking
msgid "Confirm and Transfer Picking"
msgstr ""
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields.selection,name:sale_automatic_workflow_stock.selection__sale_workflow_process__picking_policy__one
msgid "Deliver all products at once"
msgstr ""
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields.selection,name:sale_automatic_workflow_stock.selection__sale_workflow_process__picking_policy__direct
msgid "Deliver each product when available"
msgstr ""
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields,help:sale_automatic_workflow_stock.field_sale_order__picking_policy
msgid ""
"If you deliver all products at once, the delivery order will be scheduled "
"based on the greatest product lead time. Otherwise, it will be based on the "
"shortest."
msgstr ""
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields,field_description:sale_automatic_workflow_stock.field_sale_workflow_process__picking_filter_id
msgid "Picking Filter"
msgstr ""
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields,field_description:sale_automatic_workflow_stock.field_sale_workflow_process__picking_filter_domain
msgid "Picking Filter Domain"
msgstr ""
#. module: sale_automatic_workflow_stock
#: model:ir.model,name:sale_automatic_workflow_stock.model_sale_workflow_process
#: model:ir.model.fields,field_description:sale_automatic_workflow_stock.field_stock_picking__workflow_process_id
msgid "Sale Workflow Process"
msgstr ""
#. module: sale_automatic_workflow_stock
#: model:ir.model,name:sale_automatic_workflow_stock.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: sale_automatic_workflow_stock
#: model:ir.model,name:sale_automatic_workflow_stock.model_automatic_workflow_job
msgid ""
"Scheduler that will play automatically the validation of invoices, "
"pickings..."
msgstr ""
#. module: sale_automatic_workflow_stock
#: model_terms:ir.ui.view,arch_db:sale_automatic_workflow_stock.sale_workflow_process_view_form_inherit
msgid ""
"Set selection based on a search\n"
" filter:"
msgstr ""
#. module: sale_automatic_workflow_stock
#: model:ir.model.fields,field_description:sale_automatic_workflow_stock.field_sale_order__picking_policy
#: model:ir.model.fields,field_description:sale_automatic_workflow_stock.field_sale_workflow_process__picking_policy
msgid "Shipping Policy"
msgstr ""
#. module: sale_automatic_workflow_stock
#: model:ir.model,name:sale_automatic_workflow_stock.model_stock_move
msgid "Stock Move"
msgstr ""
#. module: sale_automatic_workflow_stock
#: model:ir.model,name:sale_automatic_workflow_stock.model_stock_picking
msgid "Transfer"
msgstr ""

View File

@@ -0,0 +1,5 @@
from . import automatic_workflow_job
from . import sale_order
from . import sale_workflow_process
from . import stock_move
from . import stock_picking

View File

@@ -0,0 +1,48 @@
# Copyright 2011 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
# Copyright 2013 Camptocamp SA (author: Guewen Baconnier)
# Copyright 2016 Sodexis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import api, models
from odoo.tools.safe_eval import safe_eval
from odoo.addons.sale_automatic_workflow.models.automatic_workflow_job import savepoint
_logger = logging.getLogger(__name__)
class AutomaticWorkflowJob(models.Model):
"""Extend to implement automatic work-flows for stock."""
_inherit = "automatic.workflow.job"
def _do_validate_picking(self, picking, domain_filter):
"""Validate a stock.picking, filter ensure no duplication"""
if not self.env["stock.picking"].search_count(
[("id", "=", picking.id)] + domain_filter
):
return f"{picking.display_name} {picking} job bypassed"
picking.validate_picking()
return f"{picking.display_name} {picking} validate picking successfully"
@api.model
def _validate_pickings(self, picking_filter):
picking_obj = self.env["stock.picking"]
pickings = picking_obj.search(picking_filter)
_logger.debug("Pickings to validate: %s", pickings.ids)
for picking in pickings:
with savepoint(self.env.cr):
self._do_validate_picking(picking, picking_filter)
# pylint: disable=W8110
@api.model
def _handle_pickings(self, sale_workflow):
"""Override to add stock picking validation."""
super()._handle_pickings(sale_workflow)
workflow_domain = self._sale_workflow_domain(sale_workflow)
if sale_workflow.validate_picking:
self._validate_pickings(
safe_eval(sale_workflow.picking_filter_id.domain) + workflow_domain
)

View File

@@ -0,0 +1,45 @@
# Copyright 2011 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
# Copyright 2013 Camptocamp SA (author: Guewen Baconnier)
# Copyright 2016 Sodexis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class SaleOrder(models.Model):
"""Extend to add stock related workflow."""
_inherit = "sale.order"
picking_policy = fields.Selection(
compute="_compute_picking_policy", store=True, readonly=False
)
def _depends_picking_policy(self):
depends = []
if hasattr(super(), "_depends_picking_policy"):
depends = super()._depends_picking_policy()
depends.append("workflow_process_id")
return depends
@api.depends("delivery_status")
def _compute_all_qty_delivered(self):
for order in self:
order.all_qty_delivered = order.delivery_status == "full"
@api.onchange("workflow_process_id")
def _onchange_workflow_process_id(self):
if self.workflow_process_id.picking_policy:
self.picking_policy = self.workflow_process_id.picking_policy
return super()._onchange_workflow_process_id()
@api.depends(lambda self: self._depends_picking_policy())
def _compute_picking_policy(self):
res = None
if hasattr(super(), "_compute_picking_policy"):
res = super()._compute_picking_policy()
for order in self.filtered("workflow_process_id"):
order_workflow_process = order.workflow_process_id
if order_workflow_process.picking_policy:
order.picking_policy = order_workflow_process.picking_policy
return res

View File

@@ -0,0 +1,32 @@
# Copyright 2011 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
# Copyright 2013 Camptocamp SA (author: Guewen Baconnier)
# Copyright 2016 Sodexis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class SaleWorkflowProcess(models.Model):
"""Extend to add stock related workflow process."""
_inherit = "sale.workflow.process"
picking_policy = fields.Selection(
selection=[
("direct", "Deliver each product when available"),
("one", "Deliver all products at once"),
],
string="Shipping Policy",
default="direct",
)
validate_picking = fields.Boolean(string="Confirm and Transfer Picking")
picking_filter_domain = fields.Text(
string="Picking Filter Domain", related="picking_filter_id.domain"
)
picking_filter_id = fields.Many2one(
"ir.filters",
string="Picking Filter",
default=lambda self: self._default_filter(
"sale_automatic_workflow_stock.automatic_workflow_picking_filter"
),
)

View File

@@ -0,0 +1,17 @@
# Copyright 2011 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
# Copyright 2013 Camptocamp SA (author: Guewen Baconnier)
# Copyright 2016 Sodexis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
class StockMove(models.Model):
_inherit = "stock.move"
def _get_new_picking_values(self):
values = super()._get_new_picking_values()
sale = self.group_id.sale_id
if sale:
values["workflow_process_id"] = sale.workflow_process_id.id
return values

View File

@@ -0,0 +1,36 @@
# Copyright 2011 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
# Copyright 2013 Camptocamp SA (author: Guewen Baconnier)
# Copyright 2016 Sodexis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
from odoo.tools import float_compare
class StockPicking(models.Model):
_inherit = "stock.picking"
workflow_process_id = fields.Many2one(
comodel_name="sale.workflow.process", string="Sale Workflow Process"
)
def validate_picking(self):
"""Set quantities automatically and validate the pickings."""
for picking in self:
picking.action_assign()
for move in picking.move_ids.filtered(
lambda m: m.picked and m.state not in ["done", "cancel"]
):
rounding = move.product_id.uom_id.rounding
if (
float_compare(
move.quantity,
move.product_qty,
precision_rounding=rounding,
)
== -1
):
for move_line in move.move_line_ids:
move_line.quantity = move_line.quantity_product_uom
picking.with_context(skip_immediate=True, skip_sms=True).button_validate()
return True

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1,13 @@
- Guewen Baconnier
- Beau Sebastien
- Leonardo Pistone
- Stéphane Bidoul
- Damien Crier
- Alexandre Fayolle
- Sodexis
- Dave Lasley \<<dave@laslabs.com>\>
- Akim Juillerat \<<akim.juillerat@camptocamp.com>\>
- Thomas Fossoul \<<thomas@niboo.com>\>
- Silvija Butko \<<silvija@focusate.eu>\>
- Tri Doan \<\<<tridm@trobz.com>\>\>
- Chau Le \<\<<chaulb@trobz.com>\>\>

View File

@@ -0,0 +1 @@
The migration of this module from 17.0 to 18.0 was financially supported by Camptocamp.

View File

@@ -0,0 +1,9 @@
Create stock related workflows with more or less automatization and
apply it on sales orders.
Workflows are extended with:
- Apply default values:
- Packing Policy (partial, complete)
- Apply automatic actions:
- Confirm the picking

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,455 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Sale Automatic Workflow Stock</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
: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.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
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 }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="sale-automatic-workflow-stock">
<h1 class="title">Sale Automatic Workflow Stock</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:e90599c17ac75eede3497047126b77de1fd298dd20a818f82e154a1af06230d4
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<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/18.0/sale_automatic_workflow_stock"><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-18-0/sale-workflow-18-0-sale_automatic_workflow_stock"><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=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Create stock related workflows with more or less automatization and
apply it on sales orders.</p>
<p>Workflows are extended with:</p>
<ul class="simple">
<li>Apply default values:<ul>
<li>Packing Policy (partial, complete)</li>
</ul>
</li>
<li>Apply automatic actions:<ul>
<li>Confirm the picking</li>
</ul>
</li>
</ul>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-1">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-2">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-3">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-4">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="toc-entry-5">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-6">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-1">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/sale-workflow/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_automatic_workflow_stock%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-2">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-3">Authors</a></h2>
<ul class="simple">
<li>Akretion</li>
<li>Camptocamp</li>
<li>Sodexis</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-4">Contributors</a></h2>
<ul class="simple">
<li>Guewen Baconnier</li>
<li>Beau Sebastien</li>
<li>Leonardo Pistone</li>
<li>Stéphane Bidoul</li>
<li>Damien Crier</li>
<li>Alexandre Fayolle</li>
<li>Sodexis</li>
<li>Dave Lasley &lt;<a class="reference external" href="mailto:dave&#64;laslabs.com">dave&#64;laslabs.com</a>&gt;</li>
<li>Akim Juillerat &lt;<a class="reference external" href="mailto:akim.juillerat&#64;camptocamp.com">akim.juillerat&#64;camptocamp.com</a>&gt;</li>
<li>Thomas Fossoul &lt;<a class="reference external" href="mailto:thomas&#64;niboo.com">thomas&#64;niboo.com</a>&gt;</li>
<li>Silvija Butko &lt;<a class="reference external" href="mailto:silvija&#64;focusate.eu">silvija&#64;focusate.eu</a>&gt;</li>
<li>Tri Doan &lt;&lt;<a class="reference external" href="mailto:tridm&#64;trobz.com">tridm&#64;trobz.com</a>&gt;&gt;</li>
<li>Chau Le &lt;&lt;<a class="reference external" href="mailto:chaulb&#64;trobz.com">chaulb&#64;trobz.com</a>&gt;&gt;</li>
</ul>
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#toc-entry-5">Other credits</a></h2>
<p>The migration of this module from 17.0 to 18.0 was financially supported
by Camptocamp.</p>
</div>
<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>
<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>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/sale-workflow/tree/18.0/sale_automatic_workflow_stock">OCA/sale-workflow</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,2 @@
from . import test_automatic_workflow
from . import test_multicompany

View File

@@ -0,0 +1,47 @@
# Copyright 2014 Camptocamp SA (author: Guewen Baconnier)
# Copyright 2020 Camptocamp SA (author: Simone Orsi)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.addons.sale_automatic_workflow.tests.common import TestAutomaticWorkflowMixin
class TestAutomaticWorkflowStockMixin(TestAutomaticWorkflowMixin):
"""Extend to add stock related workflow."""
def create_sale_order(
self, workflow, override=None, product_type="consu", extra_product_values=None
):
extra_product_values = extra_product_values or {}
if product_type == "consu":
extra_product_values["is_storable"] = True
# Override to create stock operations for each product
order = super().create_sale_order(
workflow,
override=override,
product_type=product_type,
extra_product_values=extra_product_values,
)
# Create inventory
for line in order.order_line:
if line.product_id.is_storable:
inventory = self.env["stock.quant"].create(
{
"product_id": line.product_id.id,
"location_id": self.env.ref("stock.stock_location_stock").id,
"inventory_quantity": line.product_uom_qty,
}
)
inventory._apply_inventory()
return order
def create_full_automatic(self, override=None):
# Override to include default stock related values
if not override:
override = {}
vals = {
"picking_policy": "one",
"validate_picking": True,
}
vals.update(override)
return super().create_full_automatic(override=vals)

View File

@@ -0,0 +1,104 @@
# Copyright 2014 Camptocamp SA (author: Guewen Baconnier)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from unittest import mock
from odoo.addons.sale_automatic_workflow.tests.common import TestCommon
from .common import TestAutomaticWorkflowStockMixin
class TestAutomaticWorkflow(TestCommon, TestAutomaticWorkflowStockMixin):
"""Test sale automatic workflow with stock."""
def test_01_full_automatic(self):
workflow = self.create_full_automatic()
sale = self.create_sale_order(workflow)
self.assertEqual(sale.state, "draft")
self.assertEqual(sale.workflow_process_id, workflow)
self.run_job()
self.assertEqual(sale.state, "sale")
self.assertTrue(sale.picking_ids)
self.assertTrue(sale.invoice_ids)
invoice = sale.invoice_ids
self.assertEqual(invoice.state, "posted")
picking = sale.picking_ids
self.run_job()
self.assertEqual(picking.state, "done")
def test_02_compute_picking_policy(self):
workflow = self.create_full_automatic()
sale = self.create_sale_order(workflow)
sale.workflow_process_id = workflow.id
self.assertEqual(sale.picking_policy, "one")
workflow2 = self.create_full_automatic(override={"picking_policy": "direct"})
sale.workflow_process_id = workflow2.id
self.assertEqual(sale.picking_policy, "direct")
def test_03_create_invoice_from_sale_order(self):
workflow = self.create_full_automatic()
sale = self.create_sale_order(workflow)
line = sale.order_line[0]
self.assertFalse(workflow.invoice_service_delivery)
self.assertEqual(line.qty_delivered_method, "stock_move")
self.assertEqual(line.qty_delivered, 0.0)
# `_create_invoices` is already tested in `sale` module.
# Make sure this addon works properly in regards to it.
mock_path = "odoo.addons.sale.models.sale_order.SaleOrder._create_invoices"
with mock.patch(mock_path) as mocked:
sale._create_invoices()
mocked.assert_called()
self.assertEqual(line.qty_delivered, 0.0)
workflow.invoice_service_delivery = True
line.qty_delivered_method = "manual"
with mock.patch(mock_path) as mocked:
sale._create_invoices()
mocked.assert_called()
self.assertEqual(line.qty_delivered, 1.0)
sale.action_confirm()
# Force the state to "full"
# note : this is not needed if you have the module sale_delivery_state
# installed but sale_automatic_workflow do not depend on it
# so we just force it so we can check the sale.all_qty_delivered
sale.delivery_status = "full"
sale._compute_all_qty_delivered()
self.assertTrue(sale.all_qty_delivered)
def test_04_invoice_from_picking_with_service_product(self):
workflow = self.create_full_automatic()
product_service = self.env["product.product"].create(
{
"name": "Remodeling Service",
"categ_id": self.env.ref("product.product_category_3").id,
"standard_price": 40.0,
"list_price": 90.0,
"type": "service",
"uom_id": self.env.ref("uom.product_uom_hour").id,
"uom_po_id": self.env.ref("uom.product_uom_hour").id,
"description": "Example of product to invoice on order",
"default_code": "PRE-PAID",
"invoice_policy": "order",
}
)
product_uom_hour = self.env.ref("uom.product_uom_hour")
override = {
"order_line": [
(
0,
0,
{
"name": "Prepaid Consulting",
"product_id": product_service.id,
"product_uom_qty": 1,
"product_uom": product_uom_hour.id,
},
)
]
}
sale = self.create_sale_order(workflow, override=override)
self.run_job()
self.assertFalse(sale.picking_ids)
self.assertTrue(sale.invoice_ids)
invoice = sale.invoice_ids
self.assertEqual(invoice.workflow_process_id, sale.workflow_process_id)

View File

@@ -0,0 +1,95 @@
# Copyright 2017 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests import tagged
from odoo.addons.sale_automatic_workflow.tests.common import TestMultiCompanyCommon
@tagged("post_install", "-at_install")
class TestMultiCompany(TestMultiCompanyCommon):
"""Test stock related workflow with multi-company."""
@classmethod
def setUpClass(cls):
"""Setup data for all test cases."""
super().setUpClass()
cls.auto_wkf.validate_picking = True
def create_auto_wkf_order(self, company, customer, product, qty):
# We need to change to the proper company
# to pick up correct company dependent fields
SaleOrder = self.env["sale.order"].with_company(company)
warehouse = self.env["stock.warehouse"].search(
[("company_id", "=", company.id)], limit=1
)
self.product_uom_unit = self.env.ref("uom.product_uom_unit")
order = SaleOrder.create(
{
"partner_id": customer.id,
"company_id": company.id,
"warehouse_id": warehouse.id,
"workflow_process_id": self.auto_wkf.id,
"order_line": [
(
0,
0,
{
"name": product.name,
"product_id": product.id,
"price_unit": product.list_price,
"product_uom_qty": qty,
"product_uom": self.product_uom_unit.id,
},
)
],
}
)
return order
def test_sale_order_multicompany(self):
self.env.user.company_id = self.env.ref("base.main_company")
order_fr = self.create_auto_wkf_order(
self.company_fr, self.customer_fr, self.product_fr, 5
)
order_ch = self.create_auto_wkf_order(
self.company_ch, self.customer_ch, self.product_ch, 10
)
order_be = self.create_auto_wkf_order(
self.company_be, self.customer_be, self.product_be, 10
)
order_fr_daughter = self.create_auto_wkf_order(
self.company_fr_daughter,
self.customer_fr_daughter,
self.product_fr_daughter,
4,
)
self.assertEqual(order_fr.state, "draft")
self.assertEqual(order_ch.state, "draft")
self.assertEqual(order_be.state, "draft")
self.assertEqual(order_fr_daughter.state, "draft")
self.env["automatic.workflow.job"].run()
self.assertTrue(order_fr.picking_ids)
self.assertTrue(order_ch.picking_ids)
self.assertTrue(order_be.picking_ids)
self.assertEqual(order_fr.picking_ids.state, "done")
self.assertEqual(order_ch.picking_ids.state, "done")
self.assertEqual(order_be.picking_ids.state, "done")
invoice_fr = order_fr.invoice_ids
invoice_ch = order_ch.invoice_ids
invoice_be = order_be.invoice_ids
invoice_fr_daughter = order_fr_daughter.invoice_ids
self.assertEqual(invoice_fr.state, "posted")
self.assertEqual(invoice_fr.journal_id.company_id, order_fr.company_id)
self.assertEqual(invoice_ch.state, "posted")
self.assertEqual(invoice_ch.journal_id.company_id, order_ch.company_id)
self.assertEqual(invoice_be.state, "posted")
self.assertEqual(invoice_be.journal_id.company_id, order_be.company_id)
self.assertEqual(invoice_fr_daughter.state, "posted")
self.assertEqual(
invoice_fr_daughter.journal_id.company_id, order_fr_daughter.company_id
)

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright (C) 2011 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
Copyright (C) 2016 Sodexis <dev@sodexis.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="view_order_form_inherit_sale_stock" model="ir.ui.view">
<field name="name">sale.order.form.sale.stock</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale_stock.view_order_form_inherit_sale_stock" />
<field name="arch" type="xml">
<xpath expr="//group[@name='sale_shipping']" position="inside">
<field name="all_qty_delivered" />
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright (C) 2011 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
Copyright (C) 2016 Sodexis <dev@sodexis.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="sale_workflow_process_view_form_inherit" model="ir.ui.view">
<field name="name">sale.workflow.process.form.stock</field>
<field name="model">sale.workflow.process</field>
<field
name="inherit_id"
ref="sale_automatic_workflow.sale_workflow_process_view_form"
/>
<field name="arch" type="xml">
<xpath expr="//div[@name='sale_order_confirm']" position="inside">
<div class="row">
<div class="col-sm-4">
<label for="picking_policy" class="o_light_label" />
</div>
<div class="col-sm-8">
<field name="picking_policy" />
</div>
</div>
</xpath>
<xpath expr="//div[@name='workflow_options']" position="inside">
<div class="row">
<div class="col-sm-4">
<label for="validate_picking" class="col-lg-7 o_light_label" />
<field name="validate_picking" nolabel="1" />
</div>
<div class="col-sm-8">
<label
for="picking_filter_id"
required="validate_picking"
invisible="not validate_picking"
/>
<div
required="validate_picking"
invisible="not validate_picking"
>
<field
name="picking_filter_domain"
widget="domain"
invisible="not picking_filter_id"
options="{'model': 'stock.picking'}"
/>
<div
class="oe_edit_only oe_inline"
> Set selection based on a search
filter: <field
name="picking_filter_id"
domain="[('model_id', '=', 'stock.picking')]"
class="oe_inline"
context="{'default_model_id': 'stock.picking', 'default_active': False, 'active_test': False}"
can_create="true"
can_write="true"
/>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
<record id="sale_workflow_process_view_tree_inherit" model="ir.ui.view">
<field name="name">sale.workflow.process.tree.stock</field>
<field name="model">sale.workflow.process</field>
<field
name="inherit_id"
ref="sale_automatic_workflow.sale_workflow_process_view_tree"
/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="picking_policy" />
</field>
<field name="validate_order" position="after">
<field name="validate_picking" />
</field>
</field>
</record>
</odoo>