Skip to content

Commit

Permalink
[ADD] sale_stock_prebook: Add process to prebook a sale order's stock…
Browse files Browse the repository at this point in the history
… before confirming it
  • Loading branch information
mt-software-de committed Feb 5, 2025
1 parent a78ff00 commit cfe2d01
Show file tree
Hide file tree
Showing 20 changed files with 868 additions and 0 deletions.
93 changes: 93 additions & 0 deletions sale_stock_prebook/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
==================
sale_stock_prebook
==================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:5b9cb364c10d40c3c7dc3e952ef0b8de29ef9049481266dd5638c240d3d3cd94
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/sale-workflow/tree/14.0/sale_stock_prebook
: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-14-0/sale-workflow-14-0-sale_stock_prebook
: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=14.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

Add process to prebook a sale order's stock before confirming it

**Table of contents**

.. contents::
:local:

Usage
=====

On the sale.order view you will find two new buttons "Reserve Stock" and "Release reservation"
which are only shown if the state is in "Quotation" or "Quotation Sent".

The reservation creates the picking/moves via a procurement run by placing a real move.

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_stock_prebook%0Aversion:%2014.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
~~~~~~~

* MT Software
* BCIM

Contributors
~~~~~~~~~~~~

* Michael Tietz (MT Software) <[email protected]>

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.

.. |maintainer-mt-software-de| image:: https://github.com/mt-software-de.png?size=40px
:target: https://github.com/mt-software-de
:alt: mt-software-de

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-mt-software-de|

This module is part of the `OCA/sale-workflow <https://github.com/OCA/sale-workflow/tree/14.0/sale_stock_prebook>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions sale_stock_prebook/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
18 changes: 18 additions & 0 deletions sale_stock_prebook/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2023 Michael Tietz (MT Software) <[email protected]>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
{
"name": "sale_stock_prebook",
"summary": "Add process to prebook a sale order's stock before confirming it",
"version": "14.0.1.0.0",
"author": "MT Software, BCIM, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/sale-workflow",
"data": [
"views/sale_views.xml",
"views/stock_location_route_views.xml",
],
"depends": [
"sale_stock",
],
"maintainers": ["mt-software-de"],
"license": "LGPL-3",
}
5 changes: 5 additions & 0 deletions sale_stock_prebook/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import sale
from . import stock_move
from . import stock_rule
from . import report_stock_forecasted
from . import stock_location_route
13 changes: 13 additions & 0 deletions sale_stock_prebook/models/report_stock_forecasted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2023 Michael Tietz (MT Software) <[email protected]>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from odoo import models
from odoo.osv import expression


class ReplenishmentReport(models.AbstractModel):
_inherit = "report.stock.report_product_product_replenishment"

def _product_sale_domain(self, product_template_ids, product_variant_ids):
domain = super()._product_sale_domain(product_template_ids, product_variant_ids)
domain = expression.AND([domain, [("order_id.stock_is_reserved", "=", False)]])
return domain
110 changes: 110 additions & 0 deletions sale_stock_prebook/models/sale.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Copyright 2023 Michael Tietz (MT Software) <[email protected]>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from odoo import api, fields, models


class SaleOrderLine(models.Model):
_inherit = "sale.order.line"

def _prepare_reserve_procurement_values(self, group_id=None):
values = self._prepare_procurement_values(group_id)
values["used_for_sale_reservation"] = True
return values

def _should_prebook_stock(self):
"""Checks if SOL product has no_sale_stock_prebook set
to know if we need to reserve it or not"""
self.ensure_one()
for route in self.product_id.route_ids:
if route.no_sale_stock_prebook:
return False
return True

def _prepare_reserve_procurement(self, group):
"""Adjusts UOM qty for product, makes list of field values for
procurement group"""
product_qty, procurement_uom = self.product_uom._adjust_uom_quantities(
self.product_uom_qty, self.product_id.uom_id
)
return self.env["procurement.group"].Procurement(
self.product_id,
product_qty,
procurement_uom,
self.order_id.partner_shipping_id.property_stock_customer,
self.product_id.display_name,
group.name,
self.order_id.company_id,
self._prepare_reserve_procurement_values(group_id=group),
)

def _prepare_reserve_procurements(self, group):
"""Prepares list of dicts - reserve procurements"""
procurements = []
for line in self:
if not line._should_prebook_stock():
continue
procurements.append(line._prepare_reserve_procurement(group))
return procurements


class SaleOrder(models.Model):
_inherit = "sale.order"

stock_is_reserved = fields.Boolean(
"Stock is reserved",
compute="_compute_stock_is_reserved",
store=True,
)

def _get_reservation_pickings(self):
return self.picking_ids.filtered(
lambda p: any(m.used_for_sale_reservation for m in p.move_lines)
)

@api.depends("picking_ids.move_lines.used_for_sale_reservation")
def _compute_stock_is_reserved(self):
for rec in self:
rec.stock_is_reserved = (rec._get_reservation_pickings() and True) or False

def _action_cancel(self):
self.release_reservation()
return super()._action_cancel()

def _action_confirm(self):
self.release_reservation()
return super()._action_confirm()

def _prepare_reserve_procurement_group_values(self):
self.ensure_one()
values = self.order_line[0]._prepare_procurement_group_vals()
values["name"] = f"Reservation for {values['name']}"
return values

def _create_reserve_procurement_group(self):
return self.env["procurement.group"].create(
self._prepare_reserve_procurement_group_values()
)

def reserve_stock(self):
self = self.filtered(
lambda s: not s.stock_is_reserved
and s.state in ["draft", "sent"]
or not s.order_line
)
if not self:
return

self = self.with_context(sale_stock_prebook_stop_proc_run=True)
procurements = []

for order in self:
group = order._create_reserve_procurement_group()
procurements += order.order_line._prepare_reserve_procurements(group)
if procurements:
self.env["procurement.group"].run(procurements)

def release_reservation(self):
pickings = self._get_reservation_pickings()
pickings.action_cancel()
pickings.group_id.sudo().unlink()
pickings.sudo().unlink()
12 changes: 12 additions & 0 deletions sale_stock_prebook/models/stock_location_route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright 2023 Raumschmiede GmbH
from odoo import fields, models


class Route(models.Model):
_inherit = "stock.location.route"

no_sale_stock_prebook = fields.Boolean(
string="No Sale Stock Prebook",
help="If set no stock will be prebooked, "
"for configured Products with this route",
)
13 changes: 13 additions & 0 deletions sale_stock_prebook/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2023 Michael Tietz (MT Software) <[email protected]>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from odoo import fields, models


class StockMove(models.Model):
_inherit = "stock.move"

used_for_sale_reservation = fields.Boolean(default=False)

def _action_assign(self):
self = self.filtered(lambda m: not m.used_for_sale_reservation)
return super(StockMove, self)._action_assign()
21 changes: 21 additions & 0 deletions sale_stock_prebook/models/stock_rule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2023 Michael Tietz (MT Software) <[email protected]>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from odoo import models


class StockRule(models.Model):
_inherit = "stock.rule"

def _get_custom_move_fields(self):
res = super()._get_custom_move_fields()
res.append("used_for_sale_reservation")
return res

def _run_pull(self, procurements):
if not self.env.context.get("sale_stock_prebook_stop_proc_run"):
return super()._run_pull(procurements)
actions_to_run = []
for procurement, rule in procurements:
if rule.picking_type_id.code == "outgoing":
actions_to_run.append((procurement, rule))
super()._run_pull(actions_to_run)
1 change: 1 addition & 0 deletions sale_stock_prebook/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Michael Tietz (MT Software) <[email protected]>
1 change: 1 addition & 0 deletions sale_stock_prebook/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add process to prebook a sale order's stock before confirming it
4 changes: 4 additions & 0 deletions sale_stock_prebook/readme/USAGE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
On the sale.order view you will find two new buttons "Reserve Stock" and "Release reservation"
which are only shown if the state is in "Quotation" or "Quotation Sent".

The reservation creates the picking/moves via a procurement run by placing a real move.
Loading

0 comments on commit cfe2d01

Please sign in to comment.