mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[IMP] stock_reserve: black, isort, prettier
This commit is contained in:
committed by
Víctor Martínez
parent
03c1aa983e
commit
f7df08dc6a
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Guewen Baconnier
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Guewen Baconnier
|
||||
@@ -19,25 +18,25 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
{'name': 'Stock Reservation',
|
||||
'summary': 'Stock reservations on products',
|
||||
'version': '10.0.1.0.0',
|
||||
'author': "Camptocamp,Odoo Community Association (OCA)",
|
||||
'category': 'Warehouse',
|
||||
'license': 'AGPL-3',
|
||||
'complexity': 'normal',
|
||||
'images': [],
|
||||
'website': "http://www.camptocamp.com",
|
||||
'depends': ['stock',
|
||||
],
|
||||
'demo': [],
|
||||
'data': ['view/stock_reserve.xml',
|
||||
'view/product.xml',
|
||||
'data/stock_data.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'auto_install': False,
|
||||
'test': ['test/stock_reserve.yml',
|
||||
],
|
||||
'installable': True,
|
||||
}
|
||||
{
|
||||
"name": "Stock Reservation",
|
||||
"summary": "Stock reservations on products",
|
||||
"version": "10.0.1.0.0",
|
||||
"author": "Camptocamp,Odoo Community Association (OCA)",
|
||||
"category": "Warehouse",
|
||||
"license": "AGPL-3",
|
||||
"complexity": "normal",
|
||||
"images": [],
|
||||
"website": "http://www.camptocamp.com",
|
||||
"depends": ["stock",],
|
||||
"demo": [],
|
||||
"data": [
|
||||
"view/stock_reserve.xml",
|
||||
"view/product.xml",
|
||||
"data/stock_data.xml",
|
||||
"security/ir.model.access.csv",
|
||||
],
|
||||
"auto_install": False,
|
||||
"test": ["test/stock_reserve.yml",],
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<data 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"/>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Release the stock.reservation when the validity date has
|
||||
<data 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" />
|
||||
</record>
|
||||
<!-- Release the stock.reservation when the validity date has
|
||||
passed -->
|
||||
<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 eval="True" name="active" />
|
||||
<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 eval="False" name="doall" />
|
||||
<field name="model">stock.reservation</field>
|
||||
<field name="function">release_validity_exceeded</field>
|
||||
<field name="args">()</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
<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 eval="True" name="active" />
|
||||
<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 eval="False" name="doall" />
|
||||
<field name="model">stock.reservation</field>
|
||||
<field name="function">release_validity_exceeded</field>
|
||||
<field name="args">()</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Author: Leonardo Pistone
|
||||
# Copyright 2015 Camptocamp SA
|
||||
#
|
||||
@@ -18,8 +17,9 @@
|
||||
|
||||
def migrate(cr, installed_version):
|
||||
"""Update a wrong location that is no_update in XML."""
|
||||
if installed_version == '8.0.0.1':
|
||||
cr.execute('''
|
||||
if installed_version == "8.0.0.1":
|
||||
cr.execute(
|
||||
"""
|
||||
UPDATE stock_location
|
||||
SET location_id = (
|
||||
SELECT res_id
|
||||
@@ -39,4 +39,5 @@ def migrate(cr, installed_version):
|
||||
WHERE name = 'stock_location_company'
|
||||
AND module = 'stock'
|
||||
);
|
||||
''')
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Guewen Baconnier
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Guewen Baconnier
|
||||
@@ -19,59 +18,62 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from odoo import models, fields, api
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
_inherit = "product.template"
|
||||
|
||||
reservation_count = fields.Float(
|
||||
compute='_compute_reservation_count',
|
||||
string='# Sales')
|
||||
compute="_compute_reservation_count", string="# Sales"
|
||||
)
|
||||
|
||||
@api.multi
|
||||
def _compute_reservation_count(self):
|
||||
for product in self:
|
||||
product.reservation_count = sum(
|
||||
product.product_variant_ids.mapped('reservation_count'))
|
||||
product.product_variant_ids.mapped("reservation_count")
|
||||
)
|
||||
|
||||
@api.multi
|
||||
def action_view_reservations(self):
|
||||
self.ensure_one()
|
||||
ref = 'stock_reserve.action_stock_reservation_tree'
|
||||
product_ids = self.mapped('product_variant_ids.id')
|
||||
ref = "stock_reserve.action_stock_reservation_tree"
|
||||
product_ids = self.mapped("product_variant_ids.id")
|
||||
action_dict = self.env.ref(ref).read()[0]
|
||||
action_dict['domain'] = [('product_id', 'in', product_ids)]
|
||||
action_dict['context'] = {
|
||||
'search_default_draft': 1,
|
||||
'search_default_reserved': 1
|
||||
}
|
||||
action_dict["domain"] = [("product_id", "in", product_ids)]
|
||||
action_dict["context"] = {
|
||||
"search_default_draft": 1,
|
||||
"search_default_reserved": 1,
|
||||
}
|
||||
return action_dict
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
_inherit = "product.product"
|
||||
|
||||
reservation_count = fields.Float(
|
||||
compute='_compute_reservation_count',
|
||||
string='# Sales')
|
||||
compute="_compute_reservation_count", string="# Sales"
|
||||
)
|
||||
|
||||
@api.multi
|
||||
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'))
|
||||
domain = [
|
||||
("product_id", "=", product.id),
|
||||
("state", "in", ["draft", "assigned"]),
|
||||
]
|
||||
reservations = self.env["stock.reservation"].search(domain)
|
||||
product.reservation_count = sum(reservations.mapped("product_qty"))
|
||||
|
||||
@api.multi
|
||||
def action_view_reservations(self):
|
||||
self.ensure_one()
|
||||
ref = 'stock_reserve.action_stock_reservation_tree'
|
||||
ref = "stock_reserve.action_stock_reservation_tree"
|
||||
action_dict = self.env.ref(ref).read()[0]
|
||||
action_dict['domain'] = [('product_id', '=', self.id)]
|
||||
action_dict['context'] = {
|
||||
'search_default_draft': 1,
|
||||
'search_default_reserved': 1
|
||||
}
|
||||
action_dict["domain"] = [("product_id", "=", self.id)]
|
||||
action_dict["context"] = {
|
||||
"search_default_draft": 1,
|
||||
"search_default_reserved": 1,
|
||||
}
|
||||
return action_dict
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Guewen Baconnier
|
||||
@@ -19,7 +18,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from odoo import models, fields, api
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import except_orm
|
||||
from odoo.tools.translate import _
|
||||
|
||||
@@ -46,18 +45,20 @@ class StockReservation(models.Model):
|
||||
* date_validity (once passed, the reservation will be released)
|
||||
* note
|
||||
"""
|
||||
_name = 'stock.reservation'
|
||||
_description = 'Stock Reservation'
|
||||
_inherits = {'stock.move': 'move_id'}
|
||||
|
||||
_name = "stock.reservation"
|
||||
_description = "Stock Reservation"
|
||||
_inherits = {"stock.move": "move_id"}
|
||||
|
||||
move_id = fields.Many2one(
|
||||
'stock.move',
|
||||
'Reservation Move',
|
||||
"stock.move",
|
||||
"Reservation Move",
|
||||
required=True,
|
||||
readonly=True,
|
||||
ondelete='cascade',
|
||||
index=True)
|
||||
date_validity = fields.Date('Validity Date')
|
||||
ondelete="cascade",
|
||||
index=True,
|
||||
)
|
||||
date_validity = fields.Date("Validity Date")
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
@@ -74,14 +75,13 @@ class StockReservation(models.Model):
|
||||
# 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']
|
||||
if "location_id" in fields_list and "picking_type_id" not in fields_list:
|
||||
fields_list = fields_list + ["picking_type_id"]
|
||||
|
||||
res = super(StockReservation, self).default_get(fields_list)
|
||||
|
||||
if 'product_qty' in res:
|
||||
del res['product_qty']
|
||||
if "product_qty" in res:
|
||||
del res["product_qty"]
|
||||
|
||||
# At this point picking_type_id and location_id
|
||||
# should be computed in default way:
|
||||
@@ -93,19 +93,20 @@ class StockReservation(models.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._default_picking_type_id()
|
||||
if not res.get("picking_type_id", None):
|
||||
res["picking_type_id"] = self._default_picking_type_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_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
|
||||
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
|
||||
@@ -115,7 +116,7 @@ class StockReservation(models.Model):
|
||||
"""
|
||||
try:
|
||||
location = self.env.ref(ref, raise_if_not_found=True)
|
||||
location.check_access_rule('read')
|
||||
location.check_access_rule("read")
|
||||
location_id = location.id
|
||||
except (except_orm, ValueError):
|
||||
location_id = False
|
||||
@@ -123,12 +124,12 @@ class StockReservation(models.Model):
|
||||
|
||||
@api.model
|
||||
def _default_picking_type_id(self):
|
||||
ref = 'stock.picking_type_out'
|
||||
ref = "stock.picking_type_out"
|
||||
return self.env.ref(ref, raise_if_not_found=False).id
|
||||
|
||||
@api.model
|
||||
def _default_location_dest_id(self):
|
||||
ref = 'stock_reserve.stock_location_reservation'
|
||||
ref = "stock_reserve.stock_location_reservation"
|
||||
return self.get_location_from_ref(ref)
|
||||
|
||||
@api.multi
|
||||
@@ -138,9 +139,9 @@ class StockReservation(models.Model):
|
||||
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_expected': fields.Datetime.now()})
|
||||
self.mapped('move_id').action_confirm()
|
||||
self.mapped('move_id.picking_id').action_assign()
|
||||
self.write({"date_expected": fields.Datetime.now()})
|
||||
self.mapped("move_id").action_confirm()
|
||||
self.mapped("move_id.picking_id").action_assign()
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
@@ -148,16 +149,18 @@ class StockReservation(models.Model):
|
||||
"""
|
||||
Release moves from reservation
|
||||
"""
|
||||
self.mapped('move_id').action_cancel()
|
||||
self.mapped("move_id").action_cancel()
|
||||
return True
|
||||
|
||||
@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', '=', 'assigned')]
|
||||
domain = [
|
||||
("date_validity", "<", fields.date.today()),
|
||||
("state", "=", "assigned"),
|
||||
]
|
||||
if ids:
|
||||
domain.append(('id', 'in', ids))
|
||||
domain.append(("id", "in", ids))
|
||||
self.search(domain).release()
|
||||
return True
|
||||
|
||||
@@ -167,18 +170,19 @@ class StockReservation(models.Model):
|
||||
self.release()
|
||||
return super(StockReservation, self).unlink()
|
||||
|
||||
@api.onchange('product_id')
|
||||
@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
|
||||
move = self.move_id or self.env['stock.move'].new(
|
||||
{'product_id': self.product_id})
|
||||
move = self.move_id or self.env["stock.move"].new(
|
||||
{"product_id": self.product_id}
|
||||
)
|
||||
move.onchange_product_id()
|
||||
self.name = move.name
|
||||
self.product_uom = move.product_uom
|
||||
|
||||
@api.onchange('product_uom_qty')
|
||||
@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:
|
||||
@@ -187,13 +191,12 @@ class StockReservation(models.Model):
|
||||
@api.multi
|
||||
def open_move(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref('stock.stock_move_action')
|
||||
action = self.env.ref("stock.stock_move_action")
|
||||
action_dict = action.read()[0]
|
||||
action_dict['name'] = _('Reservation Move')
|
||||
action_dict["name"] = _("Reservation Move")
|
||||
# open directly in the form view
|
||||
view_id = self.env.ref('stock.view_move_form').id
|
||||
view_id = self.env.ref("stock.view_move_form").id
|
||||
action_dict.update(
|
||||
views=[(view_id, 'form')],
|
||||
res_id=self.move_id.id,
|
||||
)
|
||||
views=[(view_id, "form")], res_id=self.move_id.id,
|
||||
)
|
||||
return action_dict
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
-
|
||||
I force recomputation of stock.location parent left/right
|
||||
-
|
||||
!python {model: stock.location}:
|
||||
- I force recomputation of stock.location parent left/right
|
||||
- ? !python {model: stock.location}
|
||||
# we need this because when running the tests at install time as is done on
|
||||
# Travis, the hook performing this operation for the new stock reservation
|
||||
# location is run after the test execution. This causes the stock level
|
||||
# computation to be wrong at the time the tests are run.
|
||||
self._parent_store_compute()
|
||||
-
|
||||
I create a product to test the stock reservation
|
||||
-
|
||||
!record {model: product.product, id: product_sorbet}:
|
||||
: self._parent_store_compute()
|
||||
- I create a product to test the stock reservation
|
||||
- !record {model: product.product, id: product_sorbet}:
|
||||
default_code: 001SORBET
|
||||
name: Sorbet
|
||||
type: product
|
||||
@@ -19,137 +15,96 @@
|
||||
standard_price: 70.0
|
||||
uom_id: product.product_uom_kgm
|
||||
uom_po_id: product.product_uom_kgm
|
||||
-
|
||||
I create a stock orderpoint for the product
|
||||
-
|
||||
!record {model: stock.warehouse.orderpoint, id: sorbet_orderpoint}:
|
||||
- I create a stock orderpoint for the product
|
||||
- !record {model: stock.warehouse.orderpoint, id: sorbet_orderpoint}:
|
||||
warehouse_id: stock.warehouse0
|
||||
location_id: stock.stock_location_stock
|
||||
product_id: product_sorbet
|
||||
product_uom: product.product_uom_kgm
|
||||
product_min_qty: 4.0
|
||||
product_max_qty: 15.0
|
||||
-
|
||||
I update the current stock of the Sorbet with 10 kgm
|
||||
-
|
||||
!record {model: stock.change.product.qty, id: change_qty}:
|
||||
- I update the current stock of the Sorbet with 10 kgm
|
||||
- !record {model: stock.change.product.qty, id: change_qty}:
|
||||
new_quantity: 10
|
||||
product_id: product_sorbet
|
||||
-
|
||||
!python {model: stock.change.product.qty}: |
|
||||
- !python {model: stock.change.product.qty}: |
|
||||
self.browse(ref('change_qty')).change_product_qty()
|
||||
-
|
||||
I check Virtual stock of Sorbet after update stock.
|
||||
-
|
||||
!python {model: product.product, id: product_sorbet}: |
|
||||
- I check Virtual stock of Sorbet after update stock.
|
||||
- !python {model: product.product, id: product_sorbet}: |
|
||||
assert self.virtual_available == 10, "Stock is not updated."
|
||||
-
|
||||
I create a stock reservation for 6 kgm
|
||||
-
|
||||
!record {model: stock.reservation, id: reserv_sorbet1}:
|
||||
- I create a stock reservation for 6 kgm
|
||||
- !record {model: stock.reservation, id: reserv_sorbet1}:
|
||||
product_id: product_sorbet
|
||||
product_uom_qty: 6.0
|
||||
product_uom: product.product_uom_kgm
|
||||
name: reserve 6 kg of sorbet for test
|
||||
-
|
||||
I confirm the reservation
|
||||
-
|
||||
!python {model: stock.reservation}: |
|
||||
- I confirm the reservation
|
||||
- !python {model: stock.reservation}: |
|
||||
self.browse(ref('reserv_sorbet1')).reserve()
|
||||
-
|
||||
I create a stock reservation for 500g
|
||||
-
|
||||
!record {model: stock.reservation, id: reserv_sorbet2}:
|
||||
- I create a stock reservation for 500g
|
||||
- !record {model: stock.reservation, id: reserv_sorbet2}:
|
||||
product_id: product_sorbet
|
||||
product_uom_qty: 500
|
||||
product_uom: product.product_uom_gram
|
||||
name: reserve 500g of sorbet for test
|
||||
-
|
||||
I confirm the reservation
|
||||
-
|
||||
!python {model: stock.reservation, id: reserv_sorbet2}: |
|
||||
- I confirm the reservation
|
||||
- !python {model: stock.reservation, id: reserv_sorbet2}: |
|
||||
self.reserve()
|
||||
-
|
||||
I check the reserved amount of the product and the template
|
||||
-
|
||||
!python {model: product.product, id: product_sorbet}: |
|
||||
- I check the reserved amount of the product and the template
|
||||
- !python {model: product.product, id: product_sorbet}: |
|
||||
assert 6.5 == self.reservation_count
|
||||
assert 6.5 == self.product_tmpl_id.reservation_count
|
||||
-
|
||||
Then the reservation should be assigned and have reserved a quant
|
||||
-
|
||||
!python {model: stock.reservation, id: reserv_sorbet2}: |
|
||||
- Then the reservation should be assigned and have reserved a quant
|
||||
- !python {model: stock.reservation, id: reserv_sorbet2}: |
|
||||
assert 'assigned' == self.state
|
||||
assert 1 == len(self.reserved_quant_ids)
|
||||
-
|
||||
I check Virtual stock of Sorbet after update reservation
|
||||
-
|
||||
!python {model: product.product, id: product_sorbet}: |
|
||||
- I check Virtual stock of Sorbet after update reservation
|
||||
- !python {model: product.product, id: product_sorbet}: |
|
||||
assert 3.5 == self.virtual_available
|
||||
-
|
||||
I run the scheduler
|
||||
-
|
||||
!python {model: procurement.order}: |
|
||||
- I run the scheduler
|
||||
- !python {model: procurement.order}: |
|
||||
self.run_scheduler()
|
||||
-
|
||||
The procurement linked to the orderpoint must be in exception (no routes configured)
|
||||
-
|
||||
!python {model: stock.warehouse.orderpoint}: |
|
||||
- The procurement linked to the orderpoint must be in exception (no routes configured)
|
||||
- !python {model: stock.warehouse.orderpoint}: |
|
||||
orderpoint = self.browse(ref('stock_reserve.sorbet_orderpoint'))
|
||||
assert orderpoint.procurement_ids[0].state == 'exception', 'procurement must be in exception as there is no rule for procurement'
|
||||
import math
|
||||
assert orderpoint.procurement_ids[0].product_qty == math.ceil(15 - 3.5), 'wrong product qty ordered'
|
||||
-
|
||||
I release the reservation
|
||||
-
|
||||
!python {model: stock.reservation}: |
|
||||
- I release the reservation
|
||||
- !python {model: stock.reservation}: |
|
||||
self.browse(ref('reserv_sorbet1')).release()
|
||||
-
|
||||
I check Virtual stock of Sorbet after update reservation
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
- I check Virtual stock of Sorbet after update reservation
|
||||
- !python {model: product.product}: |
|
||||
product = self.browse(ref('stock_reserve.product_sorbet'))
|
||||
assert product.virtual_available == 9.5, "Stock is not updated."
|
||||
-
|
||||
I set the validity of the second reservation to yesterday
|
||||
-
|
||||
!python {model: stock.reservation}: |
|
||||
- I set the validity of the second reservation to yesterday
|
||||
- !python {model: stock.reservation}: |
|
||||
import datetime
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
yesterday = datetime.date.today() - datetime.timedelta(days=1)
|
||||
yesterday = yesterday.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
self.browse(ref('reserv_sorbet2')).write({'date_validity': yesterday})
|
||||
-
|
||||
I call the function releasing expired reservations
|
||||
-
|
||||
!python {model: stock.reservation}: |
|
||||
- I call the function releasing expired reservations
|
||||
- !python {model: stock.reservation}: |
|
||||
self.release_validity_exceeded()
|
||||
-
|
||||
I check Virtual stock of Sorbet after update reservation
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
- I check Virtual stock of Sorbet after update reservation
|
||||
- !python {model: product.product}: |
|
||||
product = self.browse(ref('stock_reserve.product_sorbet'))
|
||||
product.refresh()
|
||||
assert product.virtual_available == 10.0, "Stock is not updated."
|
||||
|
||||
-
|
||||
I create a stock reservation for 3 kgm
|
||||
-
|
||||
!record {model: stock.reservation, id: reserv_sorbet3}:
|
||||
- I create a stock reservation for 3 kgm
|
||||
- !record {model: stock.reservation, id: reserv_sorbet3}:
|
||||
product_id: product_sorbet
|
||||
product_uom_qty: 3.0
|
||||
product_uom: product.product_uom_kgm
|
||||
name: reserve 3 kg of sorbet for test (release on unlink)
|
||||
-
|
||||
I confirm the reservation
|
||||
-
|
||||
!python {model: stock.reservation}: |
|
||||
- I confirm the reservation
|
||||
- !python {model: stock.reservation}: |
|
||||
self.browse(ref('reserv_sorbet3')).reserve()
|
||||
|
||||
-
|
||||
I press the open_move button on reservation and test result
|
||||
-
|
||||
!python {model: stock.reservation}: |
|
||||
- I press the open_move button on reservation and test result
|
||||
- !python {model: stock.reservation}: |
|
||||
reserv = self.browse(ref('reserv_sorbet3'))
|
||||
move = reserv.move_id
|
||||
action_dict = reserv.open_move()
|
||||
@@ -158,10 +113,8 @@
|
||||
assert action_dict['id'] == ref('stock.stock_move_action'), "action not correct"
|
||||
assert action_dict['views'][0][0] == ref('stock.view_move_form'), "action view not correct"
|
||||
|
||||
-
|
||||
I press button 'action_view_reservations' on product variant and test result
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
- I press button 'action_view_reservations' on product variant and test result
|
||||
- !python {model: product.product}: |
|
||||
product = self.browse(ref('product_sorbet'))
|
||||
action_dict = product.action_view_reservations()
|
||||
assert action_dict['res_model'] == 'stock.reservation', "action model is not 'stock.move'"
|
||||
@@ -171,10 +124,8 @@
|
||||
assert action_dict['context']['search_default_draft'] == 1, "wrong context"
|
||||
assert action_dict['context']['search_default_reserved'] == 1, "wrong context"
|
||||
|
||||
-
|
||||
I press button 'action_view_reservations' on product template and test result
|
||||
-
|
||||
!python {model: product.template}: |
|
||||
- I press button 'action_view_reservations' on product template and test result
|
||||
- !python {model: product.template}: |
|
||||
product = self.env['product.product'].browse(ref('product_sorbet'))
|
||||
product_tmpl = product.product_tmpl_id
|
||||
product_ids = product_tmpl.mapped('product_variant_ids.id')
|
||||
@@ -188,10 +139,8 @@
|
||||
assert action_dict['context']['search_default_draft'] == 1, "wrong context"
|
||||
assert action_dict['context']['search_default_reserved'] == 1, "wrong context"
|
||||
|
||||
-
|
||||
I unlink the reservation
|
||||
-
|
||||
!python {model: stock.reservation}: |
|
||||
- I unlink the reservation
|
||||
- !python {model: stock.reservation}: |
|
||||
reserv = self.browse(ref('reserv_sorbet3'))
|
||||
move = reserv.move_id
|
||||
reserv.unlink()
|
||||
|
||||
@@ -1,31 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?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"/>
|
||||
<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.template.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>
|
||||
<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.template.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>
|
||||
|
||||
@@ -1,140 +1,192 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?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" version="7.0">
|
||||
<header>
|
||||
<button name="reserve" type="object"
|
||||
<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" version="7.0">
|
||||
<header>
|
||||
<button
|
||||
name="reserve"
|
||||
type="object"
|
||||
string="Reserve"
|
||||
class="oe_highlight"
|
||||
states="draft"
|
||||
/>
|
||||
<button
|
||||
name="release"
|
||||
type="object"
|
||||
string="Release"
|
||||
class="oe_highlight"
|
||||
states="assigned,confirmed,done"
|
||||
/>
|
||||
<button
|
||||
name="open_move"
|
||||
type="object"
|
||||
string="View Reservation Move"
|
||||
/>
|
||||
<field
|
||||
name="state"
|
||||
widget="statusbar"
|
||||
statusbar_visible="draft,assigned"
|
||||
/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group name="main_grp" string="Details">
|
||||
<field name="product_id" />
|
||||
<label for="product_uom_qty" />
|
||||
<div>
|
||||
<field name="product_uom_qty" class="oe_inline" />
|
||||
<field
|
||||
name="product_uom"
|
||||
groups="product.group_uom"
|
||||
class="oe_inline"
|
||||
/>
|
||||
</div>
|
||||
<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"
|
||||
groups="stock.group_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
|
||||
string="Stock Reservations"
|
||||
version="7.0"
|
||||
colors="blue:state == 'draft';grey: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" type="object"
|
||||
icon="terp-locked"
|
||||
states="draft"
|
||||
/>
|
||||
<button
|
||||
name="release"
|
||||
type="object"
|
||||
string="Release"
|
||||
class="oe_highlight"
|
||||
states="assigned,confirmed,done"/>
|
||||
<button name="open_move" type="object"
|
||||
string="View Reservation Move"/>
|
||||
<field name="state" widget="statusbar"
|
||||
statusbar_visible="draft,assigned"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group name="main_grp" string="Details">
|
||||
<field name="product_id" />
|
||||
<label for="product_uom_qty" />
|
||||
<div>
|
||||
<field name="product_uom_qty" class="oe_inline"/>
|
||||
<field name="product_uom"
|
||||
groups="product.group_uom" class="oe_inline"/>
|
||||
</div>
|
||||
<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"
|
||||
groups="stock.group_locations">
|
||||
<field name="location_id"/>
|
||||
<field name="location_dest_id"/>
|
||||
</group>
|
||||
<group name="note" string="Notes">
|
||||
<field name="note" nolabel="1"/>
|
||||
</group>
|
||||
icon="gtk-undo"
|
||||
states="assigned,confirmed,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" version="7.0">
|
||||
<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="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>
|
||||
</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 string="Stock Reservations" version="7.0"
|
||||
colors="blue:state == 'draft';grey: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"
|
||||
icon="terp-locked"
|
||||
states="draft"/>
|
||||
<button name="release" type="object"
|
||||
string="Release"
|
||||
icon="gtk-undo"
|
||||
states="assigned,confirmed,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" version="7.0">
|
||||
<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="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>
|
||||
</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">
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a stock reservation.
|
||||
</p><p>
|
||||
</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_inventory_control"
|
||||
sequence="30"/>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem
|
||||
action="action_stock_reservation_tree"
|
||||
id="menu_action_stock_reservation"
|
||||
parent="stock.menu_stock_inventory_control"
|
||||
sequence="30"
|
||||
/>
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user