mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
prevent user from creating lots from the wizard
This commit is contained in:
@@ -3,3 +3,4 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from . import wizard
|
||||
from . import models
|
||||
|
||||
4
stock_move_location/models/__init__.py
Normal file
4
stock_move_location/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from . import stock_move
|
||||
20
stock_move_location/models/stock_move.py
Normal file
20
stock_move_location/models/stock_move.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = "stock.move"
|
||||
|
||||
location_move = fields.Boolean(
|
||||
string="Part of move location",
|
||||
help="Wether this move is a part of stock_location moves",
|
||||
)
|
||||
|
||||
@api.depends("location_move")
|
||||
def _compute_show_details_visible(self):
|
||||
super()._compute_show_details_visible()
|
||||
for move in self:
|
||||
if move.location_move:
|
||||
move.show_details_visible = True
|
||||
@@ -2,6 +2,8 @@
|
||||
# Copyright 2018 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from itertools import groupby
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
@@ -34,6 +36,19 @@ class StockMoveLocationWizard(models.TransientModel):
|
||||
def _onchange_locations(self):
|
||||
self._clear_lines()
|
||||
|
||||
@api.onchange("stock_move_location_line_ids")
|
||||
def _onchange_stock_move_location_line_ids(self):
|
||||
lines_to_update = self.stock_move_location_line_ids.filtered(
|
||||
lambda x: x.custom is True and
|
||||
not all([x.origin_location_id, x.destination_location_id])
|
||||
)
|
||||
lines_to_update.update({
|
||||
"origin_location_id": self.origin_location_id,
|
||||
"destination_location_id": self.destination_location_id,
|
||||
})
|
||||
# for an easier extension of this function
|
||||
return lines_to_update
|
||||
|
||||
def _clear_lines(self):
|
||||
origin = self.origin_location_id
|
||||
destination = self.destination_location_id
|
||||
@@ -54,8 +69,55 @@ class StockMoveLocationWizard(models.TransientModel):
|
||||
'location_dest_id': self.destination_location_id.id,
|
||||
})
|
||||
|
||||
@api.multi
|
||||
def group_lines(self):
|
||||
sorted_lines = sorted(
|
||||
self.stock_move_location_line_ids,
|
||||
key=lambda x: x.product_id,
|
||||
)
|
||||
groups = groupby(sorted_lines, key=lambda x: x.product_id)
|
||||
groups_dict = {}
|
||||
for prod, lines in groups:
|
||||
groups_dict[prod.id] = list(lines)
|
||||
return groups_dict
|
||||
|
||||
@api.multi
|
||||
def _create_moves(self, picking):
|
||||
return self.stock_move_location_line_ids.create_move_lines(picking)
|
||||
self.ensure_one()
|
||||
groups = self.group_lines()
|
||||
moves = self.env["stock.move"]
|
||||
for group, lines in groups.items():
|
||||
move = self._create_move(picking, lines)
|
||||
moves |= move
|
||||
return moves
|
||||
|
||||
def _get_move_values(self, picking, lines):
|
||||
# locations are same for the products
|
||||
location_from_id = lines[0].origin_location_id.id
|
||||
location_to_id = lines[0].destination_location_id.id
|
||||
product_id = lines[0].product_id.id
|
||||
product_uom_id = lines[0].product_uom_id.id
|
||||
qty = sum([x.move_quantity for x in lines])
|
||||
return {
|
||||
"name": "test",
|
||||
"location_id": location_from_id,
|
||||
"location_dest_id": location_to_id,
|
||||
"product_id": product_id,
|
||||
"product_uom": product_uom_id,
|
||||
"product_uom_qty": qty,
|
||||
"picking_id": picking.id,
|
||||
"location_move": True,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def _create_move(self, picking, lines):
|
||||
self.ensure_one()
|
||||
move = self.env["stock.move"].create(
|
||||
self._get_move_values(picking, lines),
|
||||
)
|
||||
for line in lines:
|
||||
line.create_move_lines(picking, move)
|
||||
return move
|
||||
|
||||
@api.multi
|
||||
def action_move_location(self):
|
||||
@@ -63,6 +125,8 @@ class StockMoveLocationWizard(models.TransientModel):
|
||||
picking = self._create_picking()
|
||||
self._create_moves(picking)
|
||||
if not self.env.context.get("planned"):
|
||||
picking.action_confirm()
|
||||
picking.action_assign()
|
||||
picking.button_validate()
|
||||
self.picking_id = picking
|
||||
return self._get_picking_action(picking.id)
|
||||
@@ -111,6 +175,7 @@ class StockMoveLocationWizard(models.TransientModel):
|
||||
'lot_id': group.get("lot_id") or False,
|
||||
'product_uom_id': product.uom_id.id,
|
||||
'move_location_wizard_id': self.id,
|
||||
'custom': False,
|
||||
})
|
||||
return product_data
|
||||
|
||||
@@ -118,7 +183,9 @@ class StockMoveLocationWizard(models.TransientModel):
|
||||
self.ensure_one()
|
||||
if not self.stock_move_location_line_ids:
|
||||
for line_val in self._get_stock_move_location_lines_values():
|
||||
self.env["wiz.stock.move.location.line"].create(line_val).id
|
||||
if line_val.get('max_quantity') <= 0:
|
||||
continue
|
||||
self.env["wiz.stock.move.location.line"].create(line_val)
|
||||
return {
|
||||
"type": "ir.actions.do_nothing",
|
||||
}
|
||||
|
||||
@@ -16,15 +16,16 @@
|
||||
<button name="clear_lines" string="Clear all" type="object" class="btn-primary"/>
|
||||
</group>
|
||||
<group name="lines">
|
||||
<field name="stock_move_location_line_ids" nolabel="1">
|
||||
<field name="stock_move_location_line_ids" nolabel="1" >
|
||||
<tree string="Inventory Details" editable="bottom" decoration-info="move_quantity != max_quantity" decoration-danger="(move_quantity < 0) or (move_quantity > max_quantity)">
|
||||
<field name="product_id" domain="[('type','=','product')]"/>
|
||||
<field name="product_uom_id" string="UoM" groups="product.group_uom"/>
|
||||
<field name="origin_location_id"/>
|
||||
<field name="destination_location_id"/>
|
||||
<field name="lot_id" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id}" groups="stock.group_production_lot"/>
|
||||
<field name="origin_location_id" readonly="1" />
|
||||
<field name="destination_location_id" readonly="1" />
|
||||
<field name="lot_id" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id}" groups="stock.group_production_lot" options="{'no_create': True}"/>
|
||||
<field name="move_quantity"/>
|
||||
<field name="max_quantity"/>
|
||||
<field name="custom" invisible="1" />
|
||||
<field name="max_quantity" attrs="{'readonly': [('custom', '!=', True)]}" />
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
@@ -48,7 +49,7 @@
|
||||
<menuitem
|
||||
id="menuitem_move_location"
|
||||
string="Move from location..."
|
||||
parent="stock.menu_stock_root"
|
||||
parent="stock.menu_stock_warehouse_mgmt"
|
||||
action="wiz_stock_move_location_action"
|
||||
sequence="99"/>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.addons import decimal_precision as dp
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools import float_compare
|
||||
|
||||
|
||||
class StockMoveLocationWizardLine(models.TransientModel):
|
||||
@@ -24,12 +25,10 @@ class StockMoveLocationWizardLine(models.TransientModel):
|
||||
origin_location_id = fields.Many2one(
|
||||
string='Origin Location',
|
||||
comodel_name='stock.location',
|
||||
readonly=True,
|
||||
)
|
||||
destination_location_id = fields.Many2one(
|
||||
string='Destination Location',
|
||||
comodel_name='stock.location',
|
||||
readonly=True,
|
||||
)
|
||||
product_uom_id = fields.Many2one(
|
||||
string='Product Unit of Measure',
|
||||
@@ -47,34 +46,50 @@ class StockMoveLocationWizardLine(models.TransientModel):
|
||||
max_quantity = fields.Float(
|
||||
string="Maximum available quantity",
|
||||
digits=dp.get_precision('Product Unit of Measure'),
|
||||
readonly=True,
|
||||
)
|
||||
custom = fields.Boolean(
|
||||
string="Custom line",
|
||||
default=True,
|
||||
)
|
||||
|
||||
@api.model
|
||||
def get_rounding(self):
|
||||
return self.env.ref("product.decimal_product_uom").digits or 3
|
||||
|
||||
@api.constrains("max_quantity", "move_quantity")
|
||||
def _contraints_max_move_quantity(self):
|
||||
def _constraint_max_move_quantity(self):
|
||||
for record in self:
|
||||
if (record.move_quantity > record.max_quantity or
|
||||
record.move_quantity < 0):
|
||||
if (float_compare(
|
||||
record.move_quantity,
|
||||
record.max_quantity, self.get_rounding()) == 1 or
|
||||
float_compare(record.move_quantity, 0.0,
|
||||
self.get_rounding()) == -1):
|
||||
raise ValidationError(_(
|
||||
"Move quantity can not exceed max quantity or be negative"
|
||||
))
|
||||
|
||||
def create_move_lines(self, picking):
|
||||
def create_move_lines(self, picking, move):
|
||||
for line in self:
|
||||
values = line._get_move_line_values(picking, move)
|
||||
if values.get("qty_done") <= 0:
|
||||
continue
|
||||
self.env["stock.move.line"].create(
|
||||
self._get_move_line_values(line, picking)
|
||||
values
|
||||
)
|
||||
return True
|
||||
|
||||
def _get_move_line_values(self, line, picking):
|
||||
@api.multi
|
||||
def _get_move_line_values(self, picking, move):
|
||||
self.ensure_one()
|
||||
return {
|
||||
"product_id": line.product_id.id,
|
||||
"lot_id": line.lot_id.id,
|
||||
"location_id": line.origin_location_id.id,
|
||||
"location_dest_id": line.destination_location_id.id,
|
||||
"qty_done": line._get_available_quantity(),
|
||||
"product_uom_id": line.product_uom_id.id,
|
||||
"product_id": self.product_id.id,
|
||||
"lot_id": self.lot_id.id,
|
||||
"location_id": self.origin_location_id.id,
|
||||
"location_dest_id": self.destination_location_id.id,
|
||||
"qty_done": self._get_available_quantity(),
|
||||
"product_uom_id": self.product_uom_id.id,
|
||||
"picking_id": picking.id,
|
||||
"move_id": move.id,
|
||||
}
|
||||
|
||||
def _get_available_quantity(self):
|
||||
@@ -85,10 +100,20 @@ class StockMoveLocationWizardLine(models.TransientModel):
|
||||
self.ensure_one()
|
||||
if not self.product_id:
|
||||
return 0
|
||||
if self.env.context.get("planned"):
|
||||
# for planned transfer we don't care about the amounts at all
|
||||
return self.move_quantity
|
||||
# switched to sql here to improve performance and lower db queries
|
||||
self.env.cr.execute(self._get_specific_quants_sql())
|
||||
available_qty = self.env.cr.fetchone()[0]
|
||||
if available_qty < self.move_quantity:
|
||||
available_qty = self.env.cr.fetchone()
|
||||
if not available_qty:
|
||||
# if it is immediate transfer and product doesn't exist in that
|
||||
# location -> make the transfer of 0.
|
||||
return 0
|
||||
available_qty = available_qty[0]
|
||||
if float_compare(
|
||||
available_qty,
|
||||
self.move_quantity, self.get_rounding()) == -1:
|
||||
return available_qty
|
||||
return self.move_quantity
|
||||
|
||||
@@ -109,3 +134,17 @@ class StockMoveLocationWizardLine(models.TransientModel):
|
||||
product=self.product_id.id,
|
||||
lot=lot,
|
||||
)
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
res = super().create(vals)
|
||||
# update of wizard lines is extremely buggy
|
||||
# so i have to handle this additionally in create
|
||||
if not all([res.origin_location_id, res.destination_location_id]):
|
||||
or_loc_id = res.move_location_wizard_id.origin_location_id.id
|
||||
des_loc_id = res.move_location_wizard_id.destination_location_id.id
|
||||
res.write({
|
||||
"origin_location_id": or_loc_id,
|
||||
"destination_location_id": des_loc_id,
|
||||
})
|
||||
return res
|
||||
|
||||
Reference in New Issue
Block a user