diff --git a/stock_move_location/__init__.py b/stock_move_location/__init__.py
index 93569fb46..91ba63a47 100644
--- a/stock_move_location/__init__.py
+++ b/stock_move_location/__init__.py
@@ -3,3 +3,4 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from . import wizard
+from . import models
diff --git a/stock_move_location/models/__init__.py b/stock_move_location/models/__init__.py
new file mode 100644
index 000000000..d8a43735d
--- /dev/null
+++ b/stock_move_location/models/__init__.py
@@ -0,0 +1,4 @@
+# Copyright 2019 Camptocamp SA
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+from . import stock_move
diff --git a/stock_move_location/models/stock_move.py b/stock_move_location/models/stock_move.py
new file mode 100644
index 000000000..b9c4883ea
--- /dev/null
+++ b/stock_move_location/models/stock_move.py
@@ -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
diff --git a/stock_move_location/wizard/stock_move_location.py b/stock_move_location/wizard/stock_move_location.py
index eae00cb72..65c39134b 100644
--- a/stock_move_location/wizard/stock_move_location.py
+++ b/stock_move_location/wizard/stock_move_location.py
@@ -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",
}
diff --git a/stock_move_location/wizard/stock_move_location.xml b/stock_move_location/wizard/stock_move_location.xml
index 263366e1d..6ae9831ed 100755
--- a/stock_move_location/wizard/stock_move_location.xml
+++ b/stock_move_location/wizard/stock_move_location.xml
@@ -16,15 +16,16 @@
-
+
-
-
-
+
+
+
-
+
+
@@ -48,7 +49,7 @@
diff --git a/stock_move_location/wizard/stock_move_location_line.py b/stock_move_location/wizard/stock_move_location_line.py
index b05ecae98..02abadacc 100644
--- a/stock_move_location/wizard/stock_move_location_line.py
+++ b/stock_move_location/wizard/stock_move_location_line.py
@@ -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