mirror of
https://github.com/bringout/oca-workflow-process.git
synced 2026-04-24 14:32:04 +02:00
Initial commit: OCA Workflow Process packages (456 packages)
This commit is contained in:
commit
d366e42934
18799 changed files with 1284507 additions and 0 deletions
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Configuration
|
||||
|
||||
Refer to Odoo settings for stock_reserve. Configure related models, access rights, and options as needed.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Controllers
|
||||
|
||||
This module does not define custom HTTP controllers.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Dependencies
|
||||
|
||||
This addon depends on:
|
||||
|
||||
- [stock](../../odoo-bringout-oca-ocb-stock)
|
||||
|
|
@ -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.
|
||||
|
|
@ -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"
|
||||
```
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Reports
|
||||
|
||||
This module does not define custom reports.
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Wizards
|
||||
|
||||
This module does not include UI wizards.
|
||||
|
|
@ -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",
|
||||
]
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1 @@
|
|||
* Review multicompany. Take a look of [this](https://github.com/OCA/stock-logistics-warehouse/pull/1346) PR
|
||||
|
|
@ -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*
|
||||
|
|
@ -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
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
|
|
@ -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&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 > 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>
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import test_stock_reserve
|
||||
|
|
@ -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)
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue