diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 3ba6dcb1399..b90f563c354 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -306,7 +306,7 @@ class AccountBankStatement(models.Model): #try to assign partner to bank_statement_line stl_to_assign = st_lines_left.filtered(lambda stl: not stl.partner_id) - refs = {stl_to_assign.mapped('name')} + refs = set(stl_to_assign.mapped('name')) if stl_to_assign and refs\ and st_lines_left[0].journal_id.default_credit_account_id\ and st_lines_left[0].journal_id.default_debit_account_id: diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index db70e42b8f9..787b829816d 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -1653,17 +1653,20 @@ class AccountPartialReconcile(models.Model): return self #check if the reconcilation is full #first, gather all journal items involved in the reconciliation just created - partial_rec_set = OrderedDict.fromkeys([x for x in self]) aml_set = aml_to_balance = self.env['account.move.line'] total_debit = 0 total_credit = 0 total_amount_currency = 0 #make sure that all partial reconciliations share the same secondary currency otherwise it's not #possible to compute the exchange difference entry and it has to be done manually. - currency = list(partial_rec_set)[0].currency_id + currency = self[0].currency_id maxdate = '0000-00-00' - for partial_rec in partial_rec_set: + seen = set() + todo = set(self) + while todo: + partial_rec = todo.pop() + seen.add(partial_rec) if partial_rec.currency_id != currency: #no exchange rate entry will be created currency = None @@ -1682,13 +1685,15 @@ class AccountPartialReconcile(models.Model): #currency is recorded on the partial rec and in order to check if the reconciliation is total, we need to convert the #aml.balance in that foreign currency total_amount_currency += aml.company_id.currency_id.with_context(date=aml.date).compute(aml.balance, partial_rec.currency_id) + for x in aml.matched_debit_ids | aml.matched_credit_ids: - partial_rec_set[x] = None - partial_rec_ids = [x.id for x in partial_rec_set] + if x not in seen: + todo.add(x) + + partial_rec_ids = [x.id for x in seen] aml_ids = aml_set.ids #then, if the total debit and credit are equal, or the total amount in currency is 0, the reconciliation is full digits_rounding_precision = aml_set[0].company_id.currency_id.rounding - if (currency and float_is_zero(total_amount_currency, precision_rounding=currency.rounding)) or float_compare(total_debit, total_credit, precision_rounding=digits_rounding_precision) == 0: exchange_move_id = False if currency and aml_to_balance: diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml index d73af20294d..f924ad4ef29 100644 --- a/addons/account/views/account_invoice_view.xml +++ b/addons/account/views/account_invoice_view.xml @@ -238,7 +238,7 @@
- +
@@ -378,7 +378,7 @@
diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index 5493e5928bc..99a7872a5e4 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -196,7 +196,7 @@ class MrpWorkorder(models.Model): move_lots[0].qty_done = new_qty elif move.product_id.tracking == 'serial': # Create extra pseudo record - qty_todo = new_qty - sum(move_lots.mapped('quantity')) + qty_todo = new_qty - sum(move_lots.mapped('qty_done')) if float_compare(qty_todo, 0.0, precision_rounding=move.product_uom.rounding) > 0: while float_compare(qty_todo, 0.0, precision_rounding=move.product_uom.rounding) > 0: self.active_move_line_ids += self.env['stock.move.line'].new({ @@ -217,8 +217,8 @@ class MrpWorkorder(models.Model): for move_lot in move_lots: if qty_todo <= 0: break - if not move_lot.lot_id and qty_todo >= move_lot.quantity: - qty_todo = qty_todo - move_lot.quantity + if not move_lot.lot_id and qty_todo >= move_lot.qty_done: + qty_todo = qty_todo - move_lot.qty_done self.active_move_line_ids -= move_lot # Difference operator else: #move_lot.product_qty = move_lot.product_qty - qty_todo diff --git a/addons/mrp/views/mrp_workorder_views.xml b/addons/mrp/views/mrp_workorder_views.xml index 58856092cb9..6276cacc5f5 100644 --- a/addons/mrp/views/mrp_workorder_views.xml +++ b/addons/mrp/views/mrp_workorder_views.xml @@ -152,6 +152,9 @@ + + + diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 6334dfc6b5c..3d10872c728 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -556,7 +556,7 @@ class PurchaseOrderLine(models.Model): qty -= inv_line.uom_id._compute_quantity(inv_line.quantity, line.product_uom) line.qty_invoiced = qty - @api.depends('order_id.state', 'move_ids.state') + @api.depends('order_id.state', 'move_ids.state', 'move_ids.product_uom_qty') def _compute_qty_received(self): for line in self: if line.order_id.state not in ['purchase', 'done']: diff --git a/addons/sale_stock/models/stock.py b/addons/sale_stock/models/stock.py index 2db872ad8c7..045a8f0498c 100644 --- a/addons/sale_stock/models/stock.py +++ b/addons/sale_stock/models/stock.py @@ -36,6 +36,17 @@ class StockMove(models.Model): subtype_id=self.env.ref('mail.mt_note').id) return result + @api.multi + def write(self, vals): + res = super(StockMove, self).write(vals) + if 'product_uom_qty' in vals: + for move in self: + if move.state == 'done': + sale_order_lines = self.filtered(lambda move: move.procurement_id.sale_line_id and move.product_id.expense_policy == 'no').mapped('procurement_id.sale_line_id') + for line in sale_order_lines: + line.qty_delivered = line._get_delivered_qty() + return res + class StockPicking(models.Model): _inherit = 'stock.picking' diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 6379289bfe6..c29a941e3bb 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -741,9 +741,13 @@ class StockMove(models.Model): # the reserved quantity on the quants, convert it here in # `product_id.uom_id` (the UOM of the quants is the UOM of the product). move_lines_in = move.move_orig_ids.filtered(lambda m: m.state == 'done').mapped('move_line_ids') - keys_in = ['location_dest_id', 'lot_id', 'result_package_id', 'owner_id'] + keys_in_groupby = ['location_dest_id', 'lot_id', 'result_package_id', 'owner_id'] + + def _keys_in_sorted(ml): + return (ml.location_dest_id.id, ml.lot_id.id, ml.result_package_id.id, ml.owner_id.id) + grouped_move_lines_in = {} - for k, g in groupby(sorted(move_lines_in, key=itemgetter(*keys_in)), key=itemgetter(*keys_in)): + for k, g in groupby(sorted(move_lines_in, key=_keys_in_sorted), key=itemgetter(*keys_in_groupby)): qty_done = 0 for ml in g: qty_done += ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id) @@ -757,14 +761,18 @@ class StockMove(models.Model): moves_out_siblings_to_consider = moves_out_siblings & (assigned_moves + partially_available_moves) reserved_moves_out_siblings = moves_out_siblings.filtered(lambda m: m.state in ['partially_available', 'assigned']) move_lines_out_reserved = (reserved_moves_out_siblings | moves_out_siblings_to_consider).mapped('move_line_ids') - keys_out = ['location_id', 'lot_id', 'package_id', 'owner_id'] + keys_out_groupby = ['location_id', 'lot_id', 'package_id', 'owner_id'] + + def _keys_out_sorted(ml): + return (ml.location_id.id, ml.lot_id.id, ml.package_id.id, ml.owner_id.id) + grouped_move_lines_out = {} - for k, g in groupby(sorted(move_lines_out_done, key=itemgetter(*keys_out)), key=itemgetter(*keys_out)): + for k, g in groupby(sorted(move_lines_out_done, key=_keys_out_sorted), key=itemgetter(*keys_out_groupby)): qty_done = 0 for ml in g: qty_done += ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id) grouped_move_lines_out[k] = qty_done - for k, g in groupby(sorted(move_lines_out_reserved, key=itemgetter(*keys_out)), key=itemgetter(*keys_out)): + for k, g in groupby(sorted(move_lines_out_reserved, key=_keys_out_sorted), key=itemgetter(*keys_out_groupby)): grouped_move_lines_out[k] = sum(self.env['stock.move.line'].concat(*list(g)).mapped('product_qty')) available_move_lines = {key: grouped_move_lines_in[key] - grouped_move_lines_out.get(key, 0) for key in grouped_move_lines_in.keys()} # pop key if the quantity available amount to 0 @@ -983,18 +991,3 @@ class StockMove(models.Model): move.state = 'waiting' else: move.state = 'confirmed' - - @api.multi - def action_show_picking(self): - view = self.env.ref('stock.view_picking_form') - return { - 'name': _('Transfer'), - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'stock.picking', - 'views': [(view.id, 'form')], - 'view_id': view.id, - 'target': 'new', - 'res_id': self.id} - show_picking = action_show_picking diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 124f67ef6e2..1050d60902e 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -230,6 +230,14 @@ class StockMoveLine(models.Model): # Unreserve and reserve following move in order to have the real reserved quantity on move_line. next_moves |= ml.move_id.move_dest_ids.filtered(lambda move: move.state not in ('done', 'cancel')) res = super(StockMoveLine, self).write(vals) + + # As stock_account values according to a move's `product_uom_qty`, we consider that any + # done stock move should have its `quantity_done` equals to its `product_uom_qty`, and + # this is what move's `action_done` will do. So, we replicate the behavior here. + if updates or 'qty_done' in vals: + moves = self.filtered(lambda ml: ml.move_id.state == 'done').mapped('move_id') + for move in moves: + move.product_uom_qty = move.quantity_done next_moves.do_unreserve() next_moves.action_assign() return res diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index 338208e48e3..22ae95448e4 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -2016,6 +2016,7 @@ class StockMove(TransactionCase): move1.move_line_ids.qty_done = 1 move1.action_done() + self.assertEqual(move1.product_uom_qty, 1.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf1_location), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0.0) @@ -2024,6 +2025,7 @@ class StockMove(TransactionCase): self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf1_location), -1.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), -1.0) + self.assertEqual(move1.product_uom_qty, 2.0) def test_edit_done_move_line_9(self): """ Test that editing a done stock move line linked to an untracked product correctly and @@ -2052,12 +2054,14 @@ class StockMove(TransactionCase): move1.move_line_ids.qty_done = 1 move1.action_done() + self.assertEqual(move1.product_uom_qty, 1.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf1_location), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0.0) # edit once done, we actually moved 2 products move1.move_line_ids.qty_done = 0 + self.assertEqual(move1.product_uom_qty, 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf1_location), 1.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 1.0) @@ -2087,6 +2091,7 @@ class StockMove(TransactionCase): quant = self.env['stock.quant']._gather(self.product1, self.stock_location) self.assertEqual(len(quant), 0.0) + self.assertEqual(move1.product_uom_qty, 0.0) def test_edit_done_move_line_11(self): """ Add a move line and check if the quant is updated @@ -2112,6 +2117,7 @@ class StockMove(TransactionCase): picking.action_assign() move1.move_line_ids.qty_done = 10 picking.action_done() + self.assertEqual(move1.product_uom_qty, 10.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 10.0) self.env['stock.move.line'].create({ 'picking_id': move1.move_line_ids.picking_id.id, @@ -2124,6 +2130,7 @@ class StockMove(TransactionCase): }) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 20.0) move1.move_line_ids[1].qty_done = 5 + self.assertEqual(move1.product_uom_qty, 15.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 15.0) def test_immediate_validate_1(self): diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml index ac9c856bdb3..30ec38c8f79 100644 --- a/addons/stock/views/stock_picking_views.xml +++ b/addons/stock/views/stock_picking_views.xml @@ -257,19 +257,16 @@ - - - - - - + + + + - + -