diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 7d785e00358..e0a813c71bb 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -50,7 +50,7 @@ class StockMove(models.Model): digits=0, store=True, help='Quantity in the default UoM of the product') product_uom_qty = fields.Float( - 'Quantity', + 'Initial Demand', digits=dp.get_precision('Product Unit of Measure'), default=1.0, required=True, states={'done': [('readonly', True)]}, help="This is the quantity of products from an inventory " @@ -121,10 +121,6 @@ class StockMove(models.Model): "its current stock) to gather products. If we want to chain moves and have this one to wait for the previous," "this second option should be chosen.") scrapped = fields.Boolean('Scrapped', related='location_dest_id.scrap_location', readonly=True, store=True) - remaining_qty = fields.Float( - 'Remaining Quantity', compute='_get_remaining_qty', - digits=0, states={'done': [('readonly', True)]}, - help="Remaining Quantity in default UoM according to operations matched with this move") procurement_id = fields.Many2one('procurement.order', 'Procurement') group_id = fields.Many2one('procurement.group', 'Procurement Group', default=_default_group_id) rule_id = fields.Many2one('procurement.rule', 'Procurement Rule', ondelete='restrict', help='The procurement rule that created this stock move') @@ -135,10 +131,12 @@ class StockMove(models.Model): picking_type_id = fields.Many2one('stock.picking.type', 'Operation Type') inventory_id = fields.Many2one('stock.inventory', 'Inventory') pack_operation_ids = fields.One2many('stock.pack.operation', 'move_id') + pack_operation_nosuggest_ids = fields.One2many('stock.pack.operation', 'move_id', domain=[('product_qty', '=', 0.0)]) origin_returned_move_id = fields.Many2one('stock.move', 'Origin return move', copy=False, help='Move that created the return move') returned_move_ids = fields.One2many('stock.move', 'origin_returned_move_id', 'All returned moves', help='Optional: all returned moves created from this move') reserved_availability = fields.Float( 'Quantity Reserved', compute='_compute_reserved_availability', + digits=dp.get_precision('Product Unit of Measure'), readonly=True, help='Quantity that has already been reserved for this move') availability = fields.Float( 'Forecasted Quantity', compute='_compute_product_availability', @@ -151,7 +149,63 @@ class StockMove(models.Model): route_ids = fields.Many2many('stock.location.route', 'stock_location_route_move', 'move_id', 'route_id', 'Destination route', help="Preferred route to be followed by the procurement order") warehouse_id = fields.Many2one('stock.warehouse', 'Warehouse', help="Technical field depicting the warehouse to consider for the route selection on the next procurement (if any).") has_tracking = fields.Selection(related='product_id.tracking', string='Product with Tracking') - quantity_done = fields.Float('Quantity', compute='_qty_done_compute', digits=dp.get_precision('Product Unit of Measure')) + quantity_done = fields.Float('Quantity Done', compute='_quantity_done_compute', digits=dp.get_precision('Product Unit of Measure'), inverse='_quantity_done_set', + states={'done': [('readonly', True)]}) + show_operations = fields.Boolean(related='picking_id.picking_type_id.show_operations') + show_details_visible = fields.Boolean('Details Visible', compute='_compute_show_details_visible') + show_reserved_availability = fields.Boolean('From Supplier', compute='_compute_show_reserved_availability') + picking_code = fields.Selection(related='picking_id.picking_type_id.code', readonly=True) + product_type = fields.Selection(related='product_id.type', readonly=True) + additional = fields.Boolean("Whether the move was added after the picking's confirmation", default=False) + is_editable = fields.Boolean('Is editable when done', compute='_compute_is_editable') + is_initial_demand_editable = fields.Boolean('Is initial demand editable', compute='_compute_is_initial_demand_editable') + + @api.multi + @api.depends('has_tracking', 'pack_operation_ids', 'location_id', 'location_dest_id', 'is_editable') + def _compute_show_details_visible(self): + """ According to this field, the button that calls `action_show_details` will be displayed + to work on a move from its picking form view, or not. + """ + for move in self: + if not move.product_id: + move.show_details_visible = False + continue + + if move.is_editable: + move.show_details_visible = True + continue + + multi_locations_enabled = False + if self.user_has_groups('stock.group_stock_multi_locations'): + multi_locations_enabled = move.location_id.child_ids or move.location_dest_id.child_ids + + if move.picking_id.picking_type_id.show_operations is False\ + and move.state not in ['cancel', 'draft', 'confirmed']\ + and (multi_locations_enabled or move.has_tracking != 'none' or len(move.pack_operation_ids) > 1): + move.show_details_visible = True + else: + move.show_details_visible = False + + @api.multi + def _compute_show_reserved_availability(self): + """ This field is only of use in an attrs in the picking view, in order to hide the + "available" column if the move is coming from a supplier. + """ + for move in self: + move.show_reserved_availability = not move.location_id.usage == 'supplier' + + @api.multi + def _compute_is_editable(self): + """ This field is only of use in an attrs in the picking view, in order to show + the button to edit move when they're done. + """ + for move in self: + move.is_editable = move.state == 'done' and self.user_has_groups('stock.group_stock_manager') + + @api.multi + def _compute_is_initial_demand_editable(self): + for move in self: + move.is_initial_demand_editable = move.state not in ['done', 'cancel'] and self.user_has_groups('stock.group_stock_manager') @api.one @api.depends('product_id', 'product_uom', 'product_uom_qty') @@ -194,11 +248,6 @@ class StockMove(models.Model): detect errors. """ raise UserError(_('The requested operation cannot be processed because of a programming error setting the `product_qty` field instead of the `product_uom_qty`.')) - @api.one - def _get_remaining_qty(self): - # TODO: sle jco - pass - @api.one @api.depends('pack_operation_ids.product_qty') @@ -258,6 +307,20 @@ class StockMove(models.Model): if not self._cr.fetchone(): self._cr.execute('CREATE INDEX stock_move_product_location_index ON stock_move (product_id, location_id, location_dest_id, company_id, state)') + @api.model + def default_get(self, fields_list): + # We override the default_get to make stock moves created after the picking was confirmed + # directly as available (like a force_assign). This allows to create extra move lines in + # the fp view. + defaults = super(StockMove, self).default_get(fields_list) + if self.env.context.get('default_picking_id'): + picking_id = self.env['stock.picking'].browse(self.env.context['default_picking_id']) + if picking_id.state not in ['draft', 'confirmed']: + defaults['state'] = 'assigned' + defaults['product_uom_qty'] = 0.0 + defaults['additional'] = True + return defaults + @api.multi def name_get(self): res = [] @@ -387,10 +450,6 @@ class StockMove(models.Model): forgot some of them, use this tool instead. """ return self.filtered(lambda move: move.state not in ('done', 'cancel')) - - # Main actions - # ------------------------------------------------------------ - @api.multi def do_unreserve(self): if any(move.state in ('done', 'cancel') for move in self): @@ -443,7 +502,8 @@ class StockMove(models.Model): product = self.product_id.with_context(lang=self.partner_id.lang or self.env.user.lang) self.name = product.partner_ref self.product_uom = product.uom_id.id - self.product_uom_qty = 1.0 + if self.product_uom_qty: + self.product_uom_qty = 1.0 return {'domain': {'product_uom': [('category_id', '=', product.uom_id.category_id.id)]}} @api.onchange('date') @@ -945,4 +1005,3 @@ class StockMove(models.Model): 'target': 'new', 'res_id': self.id} show_picking = action_show_picking - diff --git a/addons/stock/models/stock_pack_operation.py b/addons/stock/models/stock_pack_operation.py index e5fd2a866e0..40dedc233ce 100644 --- a/addons/stock/models/stock_pack_operation.py +++ b/addons/stock/models/stock_pack_operation.py @@ -10,7 +10,7 @@ from odoo.tools.float_utils import float_round, float_compare, float_is_zero class PackOperation(models.Model): - _name = "stock.pack.operation" + _name = "stock.pack.operation" #TODO: change to stock.move.operation _description = "Packing Operation" _order = "result_package_id desc, id" @@ -30,11 +30,12 @@ class PackOperation(models.Model): ordered_qty = fields.Float('Ordered Quantity', digits=dp.get_precision('Product Unit of Measure')) qty_done = fields.Float('Done', default=0.0, digits=dp.get_precision('Product Unit of Measure'), copy=False) package_id = fields.Many2one('stock.quant.package', 'Source Package') + lot_id = fields.Many2one('stock.production.lot', 'Lot') + lot_name = fields.Char('Lot/Serial Number') result_package_id = fields.Many2one( 'stock.quant.package', 'Destination Package', ondelete='cascade', required=False, help="If set, the operations are packed into this package") - lot_id = fields.Many2one('stock.production.lot', 'Lot') date = fields.Datetime('Date', default=fields.Datetime.now(), required=True) owner_id = fields.Many2one('res.partner', 'Owner', help="Owner of the quants") location_id = fields.Many2one('stock.location', 'From', required=True) @@ -54,9 +55,10 @@ class PackOperation(models.Model): operation.to_loc = '%s%s' % (operation_sudo.location_dest_id.name, operation_sudo.result_package_id.name or '') @api.one + @api.depends('picking_id.picking_type_id', 'product_id.tracking') def _compute_lots_visible(self): - if self.picking_id.picking_type_id and self.product_id.tracking != 'none': # TDE FIXME: not sure correctly migrated - picking = self.picking_id + picking = self.picking_id + if picking.picking_type_id and self.product_id.tracking != 'none': # TDE FIXME: not sure correctly migrated self.lots_visible = picking.picking_type_id.use_existing_lots or picking.picking_type_id.use_create_lots else: self.lots_visible = self.product_id.tracking != 'none' diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index de261ea45d6..b848d05976c 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -40,6 +40,10 @@ class PickingType(models.Model): use_existing_lots = fields.Boolean( 'Use Existing Lots/Serial Numbers', default=True, help="If this is checked, you will be able to choose the Lots/Serial Numbers. You can also decide to not put lots in this operation type. This means it will create stock with no lot or not put a restriction on the lot taken. ") + show_operations = fields.Boolean( + 'Show Operations', default=False) + show_reserved = fields.Boolean( + 'Show Reserved', default=True) # Statistics for the kanban view last_done_picking = fields.Char('Last 10 Done Pickings', compute='_compute_last_done_picking') @@ -263,7 +267,7 @@ class Picking(models.Model): states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}) pack_operation_ids = fields.One2many( - 'stock.pack.operation', 'picking_id', 'Related Packing Operations', + 'stock.pack.operation', 'picking_id', 'Operations', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}) pack_operation_exist = fields.Boolean( @@ -296,7 +300,6 @@ class Picking(models.Model): - one of the move is assigned or partially available: partially available - otherwise in waiting or confirmed state ''' - # FIXME: goes in 3 times at each write? if not self.move_lines: self.state = 'draft' elif any(move.state == 'draft' for move in self.move_lines): # TDE FIXME: should be all ? @@ -514,7 +517,6 @@ class Picking(models.Model): # 'picking_id': pick.id # }) # Might change first element # # Link existing moves or add moves when no one is related - for ops in pick.pack_operation_ids.filtered(lambda x: not x.move_id): # Search move with this product moves = pick.move_lines.filtered(lambda x: x.product_id == ops.product_id) diff --git a/addons/stock/views/stock_move_views.xml b/addons/stock/views/stock_move_views.xml index 426cf829172..6ddb7de4ffb 100644 --- a/addons/stock/views/stock_move_views.xml +++ b/addons/stock/views/stock_move_views.xml @@ -131,12 +131,94 @@ - + + stock.move.operations.form + stock.move + 1000 + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + stock.move.operations.nosuggest.form + stock.move + 1000 + primary + + + + + + + + + + + + + + + + + + + + + + + + + + stock.move.form stock.move @@ -183,6 +265,9 @@ + + + @@ -227,6 +312,9 @@ + + +
diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml index 727927af368..e94bec27cad 100644 --- a/addons/stock/views/stock_picking_views.xml +++ b/addons/stock/views/stock_picking_views.xml @@ -46,6 +46,8 @@ + + @@ -209,12 +211,13 @@