diff --git a/addons/product_expiry/wizard/stock_quant_reserve.py b/addons/product_expiry/wizard/stock_quant_reserve.py deleted file mode 100644 index 74657173f64..00000000000 --- a/addons/product_expiry/wizard/stock_quant_reserve.py +++ /dev/null @@ -1,13 +0,0 @@ -from odoo import fields, models - - -class ReserveStockQuantLine(models.TransientModel): - _inherit = 'stock.quant.reserve.line' - - expiration_date = fields.Datetime(related='lot_id.expiration_date') - - -class ReserveStockQuant(models.TransientModel): - _inherit = 'stock.quant.reserve' - - show_expiry = fields.Boolean(related='move_id.product_id.use_expiration_date') diff --git a/addons/product_expiry/wizard/stock_quant_reserve.xml b/addons/product_expiry/wizard/stock_quant_reserve.xml deleted file mode 100644 index 994a86a0329..00000000000 --- a/addons/product_expiry/wizard/stock_quant_reserve.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - stock.quant.reserve.inherit - stock.quant.reserve - - - - - - - - - - - diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index cc08ac431d9..c8541bf02a9 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -768,7 +768,7 @@ Please change the quantity done or the rounding precision of your unit of measur if self.product_id.tracking == "serial" and self.state == "assigned": self.next_serial = self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) - + quant_mode = self.picking_type_id.code != 'incoming' return { 'name': _('Detailed Operations'), 'type': 'ir.actions.act_window', @@ -780,13 +780,13 @@ Please change the quantity done or the rounding precision of your unit of measur 'res_id': self.id, 'context': dict( self.env.context, - show_owner=self.picking_type_id.code != 'incoming', - show_lots_m2o=self.has_tracking != 'none' and (self.picking_type_id.use_existing_lots or self.state == 'done' or self.origin_returned_move_id.id), # able to create lots, whatever the value of ` use_create_lots`. + show_owner=not quant_mode, + show_quant=quant_mode, + show_lots_m2o=not quant_mode and self.has_tracking != 'none' and (self.picking_type_id.use_existing_lots or self.state == 'done' or self.origin_returned_move_id.id), # able to create lots, whatever the value of ` use_create_lots`. show_lots_text=self.has_tracking != 'none' and self.picking_type_id.use_create_lots and not self.picking_type_id.use_existing_lots and self.state != 'done' and not self.origin_returned_move_id.id, - show_source_location=self.picking_type_id.code != 'incoming', - show_destination_location=self.picking_type_id.code != 'outgoing', - show_package=not self.location_id.usage == 'supplier', - show_reserved_quantity=self.state != 'done' and self.picking_type_id.code != 'incoming' + show_destination_location=not quant_mode, + show_package=not quant_mode, + show_reserved_quantity=self.state != 'done' and quant_mode ), } diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 9287a3667bb..f3f4b943b88 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -37,7 +37,8 @@ class StockMoveLine(models.Model): compute='_compute_reserved_qty', inverse='_set_reserved_qty', store=True) reserved_uom_qty = fields.Float( 'Reserved', default=0.0, digits='Product Unit of Measure', required=True, copy=False) - qty_done = fields.Float('Done', default=0.0, digits='Product Unit of Measure', copy=False) + qty_done = fields.Float('Done', default=0.0, digits='Product Unit of Measure', copy=False, + compute='_compute_qty_done', store=True, readonly=False) package_id = fields.Many2one( 'stock.quant.package', 'Source Package', ondelete='restrict', check_company=True, @@ -82,6 +83,7 @@ class StockMoveLine(models.Model): tracking = fields.Selection(related='product_id.tracking', readonly=True) origin = fields.Char(related='move_id.origin', string='Source') description_picking = fields.Text(string="Description picking") + quant_id = fields.Many2one('stock.quant', "Pick From", store=False) # Dummy field for the detailed operation view @api.depends('product_uom_id.category_id', 'product_id.uom_id.category_id', 'move_id.product_uom', 'product_id.uom_id') def _compute_product_uom_id(self): @@ -131,6 +133,12 @@ class StockMoveLine(models.Model): detect errors. """ raise UserError(_('The requested operation cannot be processed because of a programming error setting the `reserved_qty` field instead of the `reserved_uom_qty`.')) + @api.depends('quant_id') + def _compute_qty_done(self): + for record in self: + if not record.qty_done: + record.qty_done = min(record.quant_id.quantity, record.move_id.product_qty) + @api.constrains('lot_id', 'product_id') def _check_lot_product(self): for line in self: @@ -288,6 +296,8 @@ class StockMoveLine(models.Model): vals['company_id'] = self.env['stock.move'].browse(vals['move_id']).company_id.id elif vals.get('picking_id'): vals['company_id'] = self.env['stock.picking'].browse(vals['picking_id']).company_id.id + if vals.get('quant_id'): + vals.update(self._copy_quant_info(vals)) mls = super().create(vals_list) @@ -379,6 +389,8 @@ class StockMoveLine(models.Model): ('owner_id', 'res.partner'), ('product_uom_id', 'uom.uom') ] + if vals.get('quant_id'): + vals.update(self._copy_quant_info(vals)) updates = {} for key, model in triggers: if key in vals: @@ -872,6 +884,25 @@ class StockMoveLine(models.Model): 'partner_id': self.picking_id.partner_id.id, } + def _copy_quant_info(self, vals): + quant = self.env['stock.quant'].browse(vals.get('quant_id', 0)) + if self: + uom = self.product_uom_id + elif vals.get('product_uom_id'): + uom = self.env['uom.uom'].browse(vals.get('product_uom_id')) + else: + uom = quant.product_uom_id + line_data = { + 'product_id': quant.product_id.id, + 'lot_id': quant.lot_id.id, + 'package_id': quant.package_id.id, + 'location_id': quant.location_id.id, + 'owner_id': quant.owner_id.id, + } + if not (self.qty_done or vals.get('qty_done')): + line_data['qty_done'] = uom._compute_quantity(quant.available_quantity, quant.product_uom_id, rounding_method='HALF_UP') + return line_data + def action_open_reference(self): self.ensure_one() if self.move_id: @@ -937,14 +968,3 @@ class StockMoveLine(models.Model): 'message': _("The inventory adjustments have been reverted."), } } - - def action_open_reserve_stock(self): - move_id = self.env['stock.move'].browse(self.env.context.get('default_move_id')) - return { - 'type': 'ir.actions.act_window', - 'res_model': 'stock.quant.reserve', - 'view_mode': 'form', - 'context': {'default_move_id': move_id.id}, - 'target': 'new', - 'name': _('Reserve stock: %(product)s', product=move_id.product_id.name), - } diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index 6746e5bb295..01adeba5f6c 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -523,6 +523,20 @@ class StockQuant(models.Model): 'target': 'new', } + def name_get(self): + """name that will be displayed in the detailed operation""" + name_parts = [] + for record in self: + name = [] + if self.env.user.has_group('stock.group_stock_multi_locations'): + name.append(record.location_id.display_name) + if self.env.user.has_group('stock.group_production_lot') and record.lot_id: + name.append(record.lot_id.name) + name_parts.append(name) + if name_parts: + return [(quant.id, ' - '.join(name)) for quant, name in zip(self, name_parts)] + return [] + @api.constrains('product_id') def check_product_id(self): if any(elem.product_id.type != 'product' for elem in self): diff --git a/addons/stock/static/src/fields/stock_move_line_x2_many_field.js b/addons/stock/static/src/fields/stock_move_line_x2_many_field.js new file mode 100644 index 00000000000..fa1e124b825 --- /dev/null +++ b/addons/stock/static/src/fields/stock_move_line_x2_many_field.js @@ -0,0 +1,47 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field"; +import { sprintf } from "@web/core/utils/strings"; +import { useSelectCreate } from "@web/views/fields/relational_utils"; +export class SMLX2ManyField extends X2ManyField { + setup() { + super.setup(); + const selectCreate = useSelectCreate({ + resModel: "stock.quant", + activeActions: this.activeActions, + onSelected: (resIds) => this.selectRecord(resIds), + onCreateEdit: ({ context }) => this._openRecord({ context }), + }); + + this.selectCreate = (params) => { + const p = Object.assign({}, params); + return selectCreate(p); + }; + } + + async onAdd({ context, editable } = {}) { + context = {}; + const { string } = this.props; + const title = sprintf(this.env._t("Add: %s"), string); + const domain = [ + ["product_id", "=", this.props.record.data.product_id[0]], + ["location_id", "child_of", this.props.context.default_location_id], + ]; + return this.selectCreate({ domain, context, title }); + } + + selectRecord(res_ids) { + const params = { + context: { default_quant_id: res_ids[0] }, + }; + this.addInLine(params); + } +} + +export const smlX2ManyField = { + ...x2ManyField, + component: SMLX2ManyField, +}; + +registry.category("fields").add("sml_x2_many", smlX2ManyField); diff --git a/addons/stock/static/src/widgets/stock_pick_from.js b/addons/stock/static/src/widgets/stock_pick_from.js new file mode 100644 index 00000000000..060204a5352 --- /dev/null +++ b/addons/stock/static/src/widgets/stock_pick_from.js @@ -0,0 +1,53 @@ +/** @odoo-module */ + +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { Many2OneField, many2OneField } from "@web/views/fields/many2one/many2one_field"; + +const { onWillStart} = owl; + +export class StockPickFrom extends Many2OneField { + setup() { + super.setup(); + this.user = useService('user'); + onWillStart(async () => { + const testedGroup = [['location', 'stock.group_stock_multi_locations'], ['lot', 'stock.group_production_lot']]; + const userGroups = await Promise.all( + testedGroup.map((group) => this.user.hasGroup(group[1])) + ); + this.enabledGroups = {}; + for (const [index, group] of testedGroup.entries()) { + this.enabledGroups[group[0]] = userGroups[index]; + } + }); + } + get displayName() { + return super.displayName || this._quant_display_name(); + } + + get value() { + return super.value || [0, this._quant_display_name()]; + } + + _quant_display_name() { + let name_parts = []; + if (this.props.record.data.id) { + // if location group is activated + if (this.enabledGroups?.location) { + name_parts.push(this.props.record.data.location_id?.[1]) + } + if (this.enabledGroups?.lot) { + name_parts.push(this.props.record.data.lot_id?.[1] || this.props.record.data.lot_name) + } + return name_parts.join(" - "); + } + return ""; + } +} + +export const stockPickFrom = { + ...many2OneField, + component: StockPickFrom, +}; + +registry.category("fields").add("pick_from", stockPickFrom); diff --git a/addons/stock/views/stock_move_views.xml b/addons/stock/views/stock_move_views.xml index 2ac3badae7a..96ebf8ad04b 100644 --- a/addons/stock/views/stock_move_views.xml +++ b/addons/stock/views/stock_move_views.xml @@ -157,6 +157,7 @@ + @@ -191,8 +192,11 @@ - -