Initial commit: OCA Workflow Process packages (456 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:00 +02:00
commit d366e42934
18799 changed files with 1284507 additions and 0 deletions

View file

@ -0,0 +1,46 @@
# Stock Reservation
Odoo addon: stock_reserve
## Installation
```bash
pip install odoo-bringout-oca-stock-logistics-warehouse-stock_reserve
```
## Dependencies
This addon depends on:
- stock
## Manifest Information
- **Name**: Stock Reservation
- **Version**: 16.0.1.3.1
- **Category**: Warehouse
- **License**: AGPL-3
- **Installable**: True
## Source
Based on [OCA/stock-logistics-warehouse](https://github.com/OCA/stock-logistics-warehouse) branch 16.0, addon `stock_reserve`.
## License
This package maintains the original AGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Stock_reserve Module - stock_reserve
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for stock_reserve. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,5 @@
# Dependencies
This addon depends on:
- [stock](../../odoo-bringout-oca-ocb-stock)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon stock_reserve or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-stock-logistics-warehouse-stock_reserve"
# or
uv pip install odoo-bringout-oca-stock-logistics-warehouse-stock_reserve"
```

View file

@ -0,0 +1,11 @@
# Models
Detected core models and extensions in stock_reserve.
```mermaid
classDiagram
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: stock_reserve. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon stock_reserve
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,34 @@
# Security
Access control and security definitions in stock_reserve.
## Access Control Lists (ACLs)
Model access permissions defined in:
- **[ir.model.access.csv](../stock_reserve/security/ir.model.access.csv)**
- 3 model access rules
## Record Rules
Row-level security rules defined in:
```mermaid
graph TB
subgraph "Security Layers"
A[Users] --> B[Groups]
B --> C[Access Control Lists]
C --> D[Models]
B --> E[Record Rules]
E --> F[Individual Records]
end
```
Security files overview:
- **[ir.model.access.csv](../stock_reserve/security/ir.model.access.csv)**
- Model access permissions (CRUD rights)
Notes
- Access Control Lists define which groups can access which models
- Record Rules provide row-level security (filter records by user/group)
- Security groups organize users and define permission sets
- All security is enforced at the ORM level by Odoo

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon stock_reserve
```

View file

@ -0,0 +1,3 @@
# Wizards
This module does not include UI wizards.

View file

@ -0,0 +1,42 @@
[project]
name = "odoo-bringout-oca-stock-logistics-warehouse-stock_reserve"
version = "16.0.0"
description = "Stock Reservation - Stock reservations on products"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-ocb-stock>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["stock_reserve"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]

View file

@ -0,0 +1,101 @@
=================
Stock Reservation
=================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:bb54f1619a7a631de2bbfffc03d59f7485003f21e9f600680460d8a071265978
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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%2Fstock--logistics--warehouse-lightgray.png?logo=github
:target: https://github.com/OCA/stock-logistics-warehouse/tree/16.0/stock_reserve
:alt: OCA/stock-logistics-warehouse
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-16-0/stock-logistics-warehouse-16-0-stock_reserve
: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/stock-logistics-warehouse&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
Allows to create stock reservations on products.
Each reservation can have a validity date, once passed, the reservation
is automatically lifted.
The reserved products are substracted from the virtual stock. It means
that if you reserved a quantity of products which bring the virtual
stock below the minimum, the orderpoint will be triggered and new
purchase orders will be generated. It also implies that the max may be
exceeded if the reservations are canceled.
If ownership of stock is active in the stock settings, you can specify the
owner on the reservation.
**Table of contents**
.. contents::
:local:
Usage
=====
To make a stock reservation:
#. Go to *Inventory > Products*.
#. Select or create one product with stock.
#. Click on *Stock Reservations* smart button.
#. Create one reservation.
#. Press the button *Reserve*.
You can release a reservation by clicking on the button *Release*
Known issues / Roadmap
======================
* Review multicompany. Take a look of [this](https://github.com/OCA/stock-logistics-warehouse/pull/1346) PR
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/stock-logistics-warehouse/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/stock-logistics-warehouse/issues/new?body=module:%20stock_reserve%0Aversion:%2016.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
~~~~~~~
* 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/stock-logistics-warehouse <https://github.com/OCA/stock-logistics-warehouse/tree/16.0/stock_reserve>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

@ -0,0 +1,3 @@
# Copyright 2013 Camptocamp SA - Guewen Baconnier
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import model

View file

@ -0,0 +1,22 @@
# Copyright 2013 Camptocamp SA - Guewen Baconnier
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Stock Reservation",
"summary": "Stock reservations on products",
"version": "16.0.1.3.1",
"author": "Camptocamp, Odoo Community Association (OCA)",
"category": "Warehouse",
"license": "AGPL-3",
"complexity": "normal",
"website": "https://github.com/OCA/stock-logistics-warehouse",
"depends": ["stock"],
"data": [
"view/stock_reserve.xml",
"view/product.xml",
"data/stock_data.xml",
"security/ir.model.access.csv",
"data/cron.xml",
],
"auto_install": False,
"installable": True,
}

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record forcecreate="True" id="ir_cron_reserve_waiting_confirmed" model="ir.cron">
<field
name="name"
>Stock reservation: Assign waiting/confirmed reserve moves</field>
<field name="model_id" ref="model_stock_reservation" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False" />
<field name="state">code</field>
<field name="code">model.assign_waiting_confirmed_reserve_moves()</field>
</record>
<record forcecreate="True" id="ir_cron_release_stock_reservation" model="ir.cron">
<field
name="name"
>Release the stock reservation having a passed validity date</field>
<field name="model_id" ref="model_stock_reservation" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False" />
<field name="state">code</field>
<field name="code">model.release_validity_exceeded()</field>
</record>
</odoo>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="stock_location_reservation" model="stock.location">
<field name="name">Reservation Stock</field>
<field name="location_id" ref="stock.stock_location_locations" />
<field name="company_id" />
</record>
</odoo>

View file

@ -0,0 +1,4 @@
# Copyright 2013 Camptocamp SA - Guewen Baconnier
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import stock_reserve
from . import product

View file

@ -0,0 +1,61 @@
# Copyright 2013 Camptocamp SA - Guewen Baconnier
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ProductTemplate(models.Model):
_inherit = "product.template"
reservation_count = fields.Float(
compute="_compute_reservation_count", string="# Sales"
)
def _compute_reservation_count(self):
for product in self:
product.reservation_count = sum(
product.product_variant_ids.mapped("reservation_count")
)
def action_view_reservations(self):
self.ensure_one()
action_dict = self.env["ir.actions.act_window"]._for_xml_id(
"stock_reserve.action_stock_reservation_tree"
)
product_ids = self.mapped("product_variant_ids.id")
action_dict["domain"] = [("product_id", "in", product_ids)]
action_dict["context"] = {
"search_default_draft": 1,
"search_default_reserved": 1,
"default_product_id": self.product_variant_ids[0].id,
}
return action_dict
class ProductProduct(models.Model):
_inherit = "product.product"
reservation_count = fields.Float(
compute="_compute_reservation_count", string="# Sales"
)
def _compute_reservation_count(self):
for product in self:
domain = [
("product_id", "=", product.id),
("state", "in", ["draft", "assigned"]),
]
reservations = self.env["stock.reservation"].search(domain)
product.reservation_count = sum(reservations.mapped("product_qty"))
def action_view_reservations(self):
self.ensure_one()
action_dict = self.env["ir.actions.act_window"]._for_xml_id(
"stock_reserve.action_stock_reservation_tree"
)
action_dict["domain"] = [("product_id", "=", self.id)]
action_dict["context"] = {
"search_default_draft": 1,
"search_default_reserved": 1,
"default_product_id": self.id,
}
return action_dict

View file

@ -0,0 +1,224 @@
# Copyright 2013 Camptocamp SA - Guewen Baconnier
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
from odoo.exceptions import except_orm
from odoo.tools import float_compare
from odoo.tools.translate import _
class StockReservation(models.Model):
"""Allow to reserve products.
The fields mandatory for the creation of a reservation are:
* product_id
* product_uom_qty
* product_uom
* name
The following fields are required but have default values that you may
want to override:
* company_id
* location_id
* location_dest_id
Optionally, you may be interested to define:
* date_validity (once passed, the reservation will be released)
* note
"""
_name = "stock.reservation"
_description = "Stock Reservation"
_inherits = {"stock.move": "move_id"}
note = fields.Text(string="Notes")
move_id = fields.Many2one(
"stock.move",
"Reservation Move",
required=True,
readonly=True,
ondelete="cascade",
index=True,
)
date_validity = fields.Date("Validity Date")
@api.model
def default_get(self, fields_list):
"""Fix default values
- Ensure default value of computed field `product_qty` is not set
as it would raise an error
- Compute default `location_id` based on default `picking_type_id`.
Note: `default_picking_type_id` may be present in context,
so code that looks for default `location_id` is implemented here,
because it relies on already calculated default
`picking_type_id`.
"""
# if there is 'location_id' field requested, ensure that
# picking_type_id is also requested, because it is required
# to compute location_id
if "location_id" in fields_list and "picking_type_id" not in fields_list:
fields_list = fields_list + ["picking_type_id"]
res = super().default_get(fields_list)
if "product_qty" in res:
del res["product_qty"]
# At this point picking_type_id and location_id
# should be computed in default way:
# 1. look up context
# 2. look up ir_values
# 3. look up property fields
# 4. look up field.default
# 5. delegate to parent model
#
# If picking_type_id is present and location_id is not, try to find
# default value for location_id
if not res.get("picking_type_id", None):
res["picking_type_id"] = (
self.env["stock.picking.type"]
.search(
[
("code", "=", "outgoing"),
("company_id", "=", res.get("company_id")),
],
limit=1,
)
.id
)
picking_type_id = res.get("picking_type_id")
if picking_type_id and not res.get("location_id", False):
picking = self.env["stock.picking"].new(
{"picking_type_id": picking_type_id}
)
picking._onchange_picking_type()
res["location_id"] = picking.location_id.id
if "location_dest_id" in fields_list:
res["location_dest_id"] = self._default_location_dest_id()
if "product_uom_qty" in fields_list:
res["product_uom_qty"] = 1.0
return res
@api.model
def get_location_from_ref(self, ref):
"""Get a location from a xmlid if allowed
:param ref: tuple (module, xmlid)
"""
try:
location = self.env.ref(ref, raise_if_not_found=True)
location.check_access_rule("read")
location_id = location.id
except (except_orm, ValueError):
location_id = False
return location_id
@api.model
def _default_location_dest_id(self):
ref = "stock_reserve.stock_location_reservation"
return self.get_location_from_ref(ref)
def reserve(self):
"""Confirm reservations
The reservation is done using the default UOM of the product.
A date until which the product is reserved can be specified.
"""
self.write({"date": fields.Datetime.now()})
# Don't call _action_confirm() method to prevent assign picking
self.mapped("move_id").write({"state": "confirmed"})
self.mapped("move_id")._action_assign()
return True
def release_reserve(self):
"""
Release moves from reservation
"""
moves = self.mapped("move_id")
moves._action_cancel()
return True
def _get_state_domain_release_reserve(self, mode):
if mode == "reserve":
return
elif mode == "release":
return "cancel"
@api.model
def release_validity_exceeded(self, ids=None):
"""Release all the reservation having an exceeded validity date"""
domain = [
("date_validity", "<", fields.date.today()),
("state", "!=", "cancel"),
]
if ids:
domain.append(("id", "in", ids))
self.env["stock.reservation"].search(domain).release_reserve()
return True
def unlink(self):
"""Release the reservation before the unlink"""
self.release_reserve()
return super().unlink()
@api.onchange("product_id")
def _onchange_product_id(self):
"""set product_uom and name from product onchange"""
# save value before reading of self.move_id as this last one erase
# product_id value
self.move_id.product_id = self.product_id
self.move_id._onchange_product_id()
self.name = self.move_id.name
self.product_uom = self.move_id.product_uom
@api.onchange("product_uom_qty")
def _onchange_quantity(self):
"""On change of product quantity avoid negative quantities"""
if not self.product_id or self.product_uom_qty <= 0.0:
self.product_uom_qty = 0.0
def open_move(self):
self.ensure_one()
action_dict = self.env["ir.actions.act_window"]._for_xml_id(
"stock.stock_move_action"
)
action_dict["name"] = _("Reservation Move")
# open directly in the form view
view_id = self.env.ref("stock.view_move_form").id
action_dict.update(
views=[(view_id, "form")],
res_id=self.move_id.id,
)
return action_dict
def write(self, vals):
res = super().write(vals)
rounding = self.product_uom.rounding
if (
"product_uom_qty" in vals
and self.state in ["confirmed", "waiting", "partially_available"]
and float_compare(
self.product_id.virtual_available, 0, precision_rounding=rounding
)
>= 0
):
self.reserve()
return res
def _get_reservations_to_assign_domain(self):
return [
("state", "in", ["confirmed", "waiting", "partially_available"]),
"|",
("date_validity", ">=", fields.date.today()),
("date_validity", "=", False),
]
@api.model
def assign_waiting_confirmed_reserve_moves(self):
reservations_to_assign = self.search(self._get_reservations_to_assign_domain())
for reservation in reservations_to_assign:
reservation.reserve()
return True

View file

@ -0,0 +1,12 @@
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
* Yannick Vaucher <yannick.vaucher@camptocamp.com>
* Alexandre Fayolle <alexandre.fayolle@camptocamp.com>
* Leonardo Pistone <leonardo.pistone@camptocamp.com>
* `Tecnativa <https://www.tecnativa.com>`_:
* Carlos Roca
* `GreenIce <https://www.greenice.com>`_:
* Fernando La Chica <fernandolachica@gmail.com>

View file

@ -0,0 +1,13 @@
Allows to create stock reservations on products.
Each reservation can have a validity date, once passed, the reservation
is automatically lifted.
The reserved products are substracted from the virtual stock. It means
that if you reserved a quantity of products which bring the virtual
stock below the minimum, the orderpoint will be triggered and new
purchase orders will be generated. It also implies that the max may be
exceeded if the reservations are canceled.
If ownership of stock is active in the stock settings, you can specify the
owner on the reservation.

View file

@ -0,0 +1 @@
* Review multicompany. Take a look of [this](https://github.com/OCA/stock-logistics-warehouse/pull/1346) PR

View file

@ -0,0 +1,9 @@
To make a stock reservation:
#. Go to *Inventory > Products*.
#. Select or create one product with stock.
#. Click on *Stock Reservations* smart button.
#. Create one reservation.
#. Press the button *Reserve*.
You can release a reservation by clicking on the button *Release*

View file

@ -0,0 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_stock_reservation_manager,stock.reservation manager,model_stock_reservation,stock.group_stock_manager,1,1,1,1
access_stock_reservation_user,stock.reservation user,model_stock_reservation,stock.group_stock_user,1,1,1,0
access_stock_reservation_all,stock.reservation all,model_stock_reservation,base.group_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_stock_reservation_manager stock.reservation manager model_stock_reservation stock.group_stock_manager 1 1 1 1
3 access_stock_reservation_user stock.reservation user model_stock_reservation stock.group_stock_user 1 1 1 0
4 access_stock_reservation_all stock.reservation all model_stock_reservation base.group_user 1 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -0,0 +1,442 @@
<!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>Stock Reservation</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
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: grey; } /* 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 {
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="stock-reservation">
<h1 class="title">Stock Reservation</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:bb54f1619a7a631de2bbfffc03d59f7485003f21e9f600680460d8a071265978
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<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/stock-logistics-warehouse/tree/16.0/stock_reserve"><img alt="OCA/stock-logistics-warehouse" src="https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/stock-logistics-warehouse-16-0/stock-logistics-warehouse-16-0-stock_reserve"><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/stock-logistics-warehouse&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Allows to create stock reservations on products.</p>
<p>Each reservation can have a validity date, once passed, the reservation
is automatically lifted.</p>
<p>The reserved products are substracted from the virtual stock. It means
that if you reserved a quantity of products which bring the virtual
stock below the minimum, the orderpoint will be triggered and new
purchase orders will be generated. It also implies that the max may be
exceeded if the reservations are canceled.</p>
<p>If ownership of stock is active in the stock settings, you can specify the
owner on the reservation.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-2">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-6">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p>To make a stock reservation:</p>
<ol class="arabic simple">
<li>Go to <em>Inventory &gt; Products</em>.</li>
<li>Select or create one product with stock.</li>
<li>Click on <em>Stock Reservations</em> smart button.</li>
<li>Create one reservation.</li>
<li>Press the button <em>Reserve</em>.</li>
</ol>
<p>You can release a reservation by clicking on the button <em>Release</em></p>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>Review multicompany. Take a look of [this](<a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/pull/1346">https://github.com/OCA/stock-logistics-warehouse/pull/1346</a>) PR</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/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/stock-logistics-warehouse/issues/new?body=module:%20stock_reserve%0Aversion:%2016.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-4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
<ul class="simple">
<li>Camptocamp</li>
</ul>
</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/stock-logistics-warehouse/tree/16.0/stock_reserve">OCA/stock-logistics-warehouse</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 @@
from . import test_stock_reserve

View file

@ -0,0 +1,70 @@
# Copyright 2021 Tecnativa - Carlos Roca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields
from odoo.tests import Form, common
class TestStockReserve(common.TransactionCase):
def setUp(self):
super().setUp()
warehouse_form = Form(self.env["stock.warehouse"])
warehouse_form.name = "Test warehouse"
warehouse_form.code = "TEST"
self.warehouse = warehouse_form.save()
product_form = Form(self.env["product.product"])
product_form.name = "Test Product"
product_form.detailed_type = "product"
self.product = product_form.save()
self.env["stock.quant"].create(
{
"product_id": self.product.id,
"location_id": self.warehouse.lot_stock_id.id,
"quantity": 10.0,
}
)
def _create_stock_reservation(self, qty):
form_reservation = Form(self.env["stock.reservation"])
form_reservation.product_id = self.product
form_reservation.product_uom_qty = qty
form_reservation.location_id = self.warehouse.lot_stock_id
return form_reservation.save()
def test_reservation_and_reservation_release(self):
reservation_1 = self._create_stock_reservation(6)
reservation_1.reserve()
self.assertFalse(reservation_1.picking_id)
self.assertEqual(self.product.virtual_available, 4)
reservation_2 = self._create_stock_reservation(1)
reservation_2.reserve()
self.assertFalse(reservation_2.picking_id)
self.assertEqual(self.product.virtual_available, 3)
reservation_1.release_reserve()
self.assertEqual(self.product.virtual_available, 9)
def test_cron_release(self):
reservation_1 = self._create_stock_reservation(6)
reservation_1.date_validity = fields.Date.from_string("2021-01-01")
reservation_1.reserve()
self.assertFalse(reservation_1.picking_id)
self.assertEqual(self.product.virtual_available, 4)
cron = self.env.ref("stock_reserve.ir_cron_release_stock_reservation")
cron.method_direct_trigger()
self.assertEqual(self.product.virtual_available, 10)
def test_cron_reserve(self):
reservation_1 = self._create_stock_reservation(11)
reservation_1.reserve()
self.assertFalse(reservation_1.picking_id)
self.assertEqual(reservation_1.state, "partially_available")
self.env["stock.quant"].create(
{
"product_id": self.product.id,
"location_id": self.warehouse.lot_stock_id.id,
"quantity": 10.0,
}
)
cron = self.env.ref("stock_reserve.ir_cron_reserve_waiting_confirmed")
cron.method_direct_trigger()
self.assertEqual(reservation_1.state, "assigned")
self.assertEqual(self.product.virtual_available, 9)

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record model="ir.ui.view" id="product_template_form_view_reservation_button">
<field name="name">product.template.reservation.button</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_only_form_view" />
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button
class="oe_inline oe_stat_button"
name="action_view_reservations"
type="object"
attrs="{'invisible':[('type', '!=', 'product')]}"
icon="fa-lock"
>
<field
string="Stock Reservations"
name="reservation_count"
widget="statinfo"
/>
</button>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="product_product_form_view_reservation_button">
<field name="name">product.product.reservation.button</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view" />
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button
class="oe_inline oe_stat_button"
name="action_view_reservations"
type="object"
attrs="{'invisible':[('type', '!=', 'product')]}"
icon="fa-lock"
>
<field
string="Stock Reservations"
name="reservation_count"
widget="statinfo"
/>
</button>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,205 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_stock_reservation_form" model="ir.ui.view">
<field name="name">stock.reservation.form</field>
<field name="model">stock.reservation</field>
<field name="arch" type="xml">
<form string="Stock Reservations">
<header>
<button
name="reserve"
type="object"
string="Reserve"
class="oe_highlight"
states="draft"
/>
<button
name="release_reserve"
type="object"
string="Release"
class="oe_highlight"
states="assigned,confirmed,partially_available,done"
/>
<button
name="open_move"
type="object"
string="View Reservation Move"
/>
<field
name="state"
widget="statusbar"
statusbar_visible="draft,assigned"
/>
</header>
<sheet>
<widget
name="web_ribbon"
title="Released"
bg_color="bg-danger"
attrs="{'invisible': [('state', '!=', 'cancel')]}"
/>
<group>
<group name="main_grp" string="Details">
<field name="company_id" invisible="1" />
<field name="product_id" />
<label for="product_uom_qty" />
<div>
<field name="product_uom_qty" class="oe_inline" />
<field
name="product_uom"
groups="uom.group_uom"
class="oe_inline"
/>
</div>
<field name="product_uom_category_id" invisible="1" />
<field name="name" />
<field name="date_validity" />
<field name="create_date" groups="base.group_no_one" />
<field
name="company_id"
groups="base.group_multi_company"
widget="selection"
/>
<field
name="restrict_partner_id"
groups="stock.group_tracking_owner"
/>
</group>
<group name="location" string="Locations">
<field name="location_id" />
<field name="location_dest_id" />
</group>
<group name="note" string="Notes">
<field name="note" nolabel="1" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_stock_reservation_tree" model="ir.ui.view">
<field name="name">stock.reservation.tree</field>
<field name="model">stock.reservation</field>
<field name="arch" type="xml">
<tree
decoration-primary="state == 'draft'"
decoration-muted="state == 'cancel'"
>
<field name="name" />
<field name="product_id" />
<field name="move_id" />
<field name="product_uom_qty" sum="Total" />
<field name="product_uom" />
<field name="date_validity" />
<field name="restrict_partner_id" groups="stock.group_tracking_owner" />
<field name="location_id" />
<field name="state" />
<button
name="reserve"
type="object"
string="Reserve"
class="oe_highlight"
states="draft"
/>
<button
name="release_reserve"
type="object"
string="Release"
class="oe_highlight"
states="assigned,confirmed,partially_available,done"
/>
</tree>
</field>
</record>
<record id="view_stock_reservation_search" model="ir.ui.view">
<field name="name">stock.reservation.search</field>
<field name="model">stock.reservation</field>
<field name="arch" type="xml">
<search string="Stock Reservations">
<filter
name="draft"
string="Draft"
domain="[('state', '=', 'draft')]"
help="Not already reserved"
/>
<filter
name="reserved"
string="Reserved"
domain="[('state', '=', 'assigned')]"
help="Moves are reserved."
/>
<filter
name="confirmed"
string="Confirmed"
domain="[('state', '=', 'confirmed')]"
help="Moves are Confirmed."
/>
<filter
name="cancel"
string="Released"
domain="[('state', '=', 'cancel')]"
help="Reservations have been released."
/>
<field name="name" />
<field name="product_id" />
<field name="move_id" />
<field name="restrict_partner_id" groups="stock.group_tracking_owner" />
<group expand="0" string="Group By...">
<filter
string="Status"
name="groupby_state"
domain="[]"
context="{'group_by': 'state'}"
/>
<filter
string="Product"
domain="[]"
name="groupby_product"
context="{'group_by': 'product_id'}"
/>
<filter
string="Product UoM"
domain="[]"
name="groupby_product_uom"
context="{'group_by': 'product_uom'}"
/>
<filter
string="Source Location"
domain="[]"
name="groupby_location"
context="{'group_by': 'location_id'}"
/>
</group>
</search>
</field>
</record>
<record id="action_stock_reservation_tree" model="ir.actions.act_window">
<field name="name">Stock Reservations</field>
<field name="res_model">stock.reservation</field>
<field name="type">ir.actions.act_window</field>
<field name="view_id" ref="view_stock_reservation_tree" />
<field name="search_view_id" ref="view_stock_reservation_search" />
<field name="context">
{
'search_default_draft': 1,
'search_default_reserved': 1,
'search_default_groupby_product': 1
}
</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a stock reservation.
</p>
<p>
This menu allow you to prepare and reserve some quantities
of products.
</p>
</field>
</record>
<menuitem
action="action_stock_reservation_tree"
id="menu_action_stock_reservation"
parent="stock.menu_stock_warehouse_mgmt"
sequence="110"
/>
</odoo>