diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml
index 5acb2ffcb9f..018f95380ff 100644
--- a/addons/account/views/report_invoice.xml
+++ b/addons/account/views/report_invoice.xml
@@ -112,6 +112,12 @@
+
+
+
+
on
@@ -122,12 +128,6 @@
-
-
-
-
|
partner.membership_stop:
res[partner.id] = 'free' if partner.free_member else 'old'
continue
+ if partner.associate_member:
+ res_state = partner.associate_member._membership_state()
+ res[partner.id] = res_state[partner.associate_member.id]
+ continue
s = 4
if partner.member_lines:
for mline in partner.member_lines:
if (mline.date_to or '0000-00-00') >= today and (mline.date_from or '0000-00-00') <= today:
- if mline.account_invoice_line.invoice_id:
+ if mline.account_invoice_line.invoice_id.partner_id == partner:
mstate = mline.account_invoice_line.invoice_id.state
if mstate == 'paid':
s = 0
inv = mline.account_invoice_line.invoice_id
- for payment in inv.payment_ids:
- if any(payment.invoice_ids.filtered(lambda inv: inv.type == 'out_refund')):
+ for ml in inv.payment_move_line_ids:
+ if any(ml.invoice_id.filtered(lambda inv: inv.type == 'out_refund')):
s = 2
break
elif mstate == 'open' and s != 0:
@@ -144,9 +150,6 @@ class Partner(models.Model):
res[partner.id] = 'none'
if partner.free_member and s != 0:
res[partner.id] = 'free'
- if partner.associate_member:
- res_state = partner.associate_member._membership_state()
- res[partner.id] = res_state[partner.associate_member.id]
return res
@api.one
diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py
index 6c56fa22275..d8f7f479d58 100644
--- a/addons/mrp/tests/test_order.py
+++ b/addons/mrp/tests/test_order.py
@@ -578,3 +578,42 @@ class TestMrpOrder(TestMrpCommon):
mo.button_mark_done()
self.assertEqual(mo.state, 'done', "Production order should be in done state.")
+
+ def test_product_produce_4(self):
+ """ Possibility to produce with a given raw material in multiple locations. """
+ self.stock_location = self.env.ref('stock.stock_location_stock')
+ self.stock_shelf_1 = self.env.ref('stock.stock_location_components')
+ self.stock_shelf_2 = self.env.ref('stock.stock_location_14')
+ mo, _, p_final, p1, p2 = self.generate_mo(qty_final=1, qty_base_1=5)
+
+ self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_1, 2)
+ self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_2, 3)
+ self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 1)
+
+ mo.action_assign()
+ ml_p1 = mo.move_raw_ids.filtered(lambda x: x.product_id == p1).mapped('move_line_ids')
+ ml_p2 = mo.move_raw_ids.filtered(lambda x: x.product_id == p2).mapped('move_line_ids')
+ self.assertEqual(len(ml_p1), 2)
+ self.assertEqual(len(ml_p2), 1)
+
+ # Add some quantity already done to force an extra move line to be created
+ ml_p1[0].qty_done = 1.0
+
+ # Produce baby!
+ product_produce = self.env['mrp.product.produce'].with_context({
+ 'active_id': mo.id,
+ 'active_ids': [mo.id],
+ }).create({
+ 'product_qty': 1.0,
+ })
+ product_produce.do_produce()
+
+ ml_p1 = mo.move_raw_ids.filtered(lambda x: x.product_id == p1).mapped('move_line_ids')
+ self.assertEqual(len(ml_p1), 3)
+ for ml in ml_p1:
+ self.assertIn(ml.qty_done, [1.0, 2.0, 3.0], 'Quantity done should be 1.0, 2.0 or 3.0')
+ self.assertEqual(sum(ml_p1.mapped('qty_done')), 6.0, 'Total qty consumed should be 6.0')
+ self.assertEqual(sum(ml_p1.mapped('product_uom_qty')), 5.0, 'Total qty reserved should be 5.0')
+
+ mo.button_mark_done()
+ self.assertEqual(mo.state, 'done', "Production order should be in done state.")
diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py
index 57bffd5d2fe..37e85825cdd 100644
--- a/addons/mrp/wizard/mrp_product_produce.py
+++ b/addons/mrp/wizard/mrp_product_produce.py
@@ -100,8 +100,10 @@ class MrpProductProduce(models.TransientModel):
if self.product_id.tracking != 'none':
qty_to_add = float_round(quantity * move.unit_factor, precision_rounding=rounding)
move._generate_consumed_move_line(qty_to_add, self.lot_id)
- else:
+ elif len(move._get_move_lines()) < 2:
move.quantity_done += float_round(quantity * move.unit_factor, precision_rounding=rounding)
+ else:
+ self._set_quantity_done(move, quantity * move.unit_factor)
for move in self.production_id.move_finished_ids:
if move.product_id.tracking == 'none' and move.state not in ('done', 'cancel'):
rounding = move.product_uom.rounding
@@ -118,6 +120,40 @@ class MrpProductProduce(models.TransientModel):
})
return {'type': 'ir.actions.act_window_close'}
+ def _set_quantity_done(self, move, qty):
+ """
+ Set the given quantity as quantity done on the move through the move lines. The method is
+ able to handle move lines with a different UoM than the move (but honestly, this would be
+ looking for trouble...).
+ @param move: stock.move
+ @param qty: quantity in the UoM of move.product_uom
+ """
+ for ml in move.move_line_ids:
+ # Convert move line qty into move uom
+ ml_qty = ml.product_uom_qty - ml.qty_done
+ if ml.product_uom_id != move.product_uom:
+ ml_qty = ml.product_uom_id._compute_quantity(ml_qty, move.product_uom, round=False)
+
+ taken_qty = min(qty, ml_qty)
+ # Convert taken qty into move line uom
+ if ml.product_uom_id != move.product_uom:
+ taken_qty = move.product_uom._compute_quantity(ml_qty, ml.product_uom_id, round=False)
+
+ # Assign qty_done and explicitly round to make sure there is no inconsistency between
+ # ml.qty_done and qty.
+ taken_qty = float_round(taken_qty, precision_rounding=ml.product_uom_id.rounding)
+ ml.qty_done += taken_qty
+ if ml.product_uom_id != move.product_uom:
+ taken_qty = ml.product_uom_id._compute_quantity(ml_qty, move.product_uom, round=False)
+ qty -= taken_qty
+
+ if float_compare(qty, 0.0, precision_rounding=move.product_uom.rounding) <= 0:
+ break
+ if float_compare(qty, 0.0, precision_rounding=move.product_uom.rounding) > 0:
+ vals = move._prepare_move_line_vals(quantity=0)
+ vals['qty_done'] = qty
+ ml = self.env['stock.move.line'].create(vals)
+
@api.multi
def check_finished_move_lots(self):
produce_move = self.production_id.move_finished_ids.filtered(lambda x: x.product_id == self.product_id and x.state not in ('done', 'cancel'))
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index ece3fc30a60..56d8500a3cd 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -180,7 +180,7 @@ class PosOrder(models.Model):
}
@api.model
- def _get_account_move_line_group_data_type_key(self, data_type, values):
+ def _get_account_move_line_group_data_type_key(self, data_type, values, options={}):
"""
Return a tuple which will be used as a key for grouping account
move lines in _create_account_move_line method.
@@ -195,10 +195,16 @@ class PosOrder(models.Model):
values['analytic_account_id'],
values['debit'] > 0)
elif data_type == 'tax':
- return ('tax',
- values['partner_id'],
- values['tax_line_id'],
- values['debit'] > 0)
+ order_id = values.pop('order_id', False)
+ tax_key = ('tax',
+ values['partner_id'],
+ values['tax_line_id'],
+ values['debit'] > 0)
+ if options.get('rounding_method') == 'round_globally':
+ tax_key = ('tax',
+ values['tax_line_id'],
+ order_id)
+ return tax_key
elif data_type == 'counter_part':
return ('counter_part',
values['partner_id'],
@@ -308,7 +314,7 @@ class PosOrder(models.Model):
'move_id': move.id,
})
- key = self._get_account_move_line_group_data_type_key(data_type, values)
+ key = self._get_account_move_line_group_data_type_key(data_type, values, {'rounding_method': rounding_method})
if not key:
return
@@ -322,6 +328,14 @@ class PosOrder(models.Model):
current_value['quantity'] = current_value.get('quantity', 0.0) + values.get('quantity', 0.0)
current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0)
current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0)
+ if key[0] == 'tax' and rounding_method == 'round_globally':
+ if current_value['debit'] - current_value['credit'] > 0:
+ current_value['debit'] = current_value['debit'] - current_value['credit']
+ current_value['credit'] = 0
+ else:
+ current_value['credit'] = current_value['credit'] - current_value['debit']
+ current_value['debit'] = 0
+
else:
grouped_data[key].append(values)
@@ -381,7 +395,8 @@ class PosOrder(models.Model):
'credit': ((tax['amount'] > 0) and tax['amount']) or 0.0,
'debit': ((tax['amount'] < 0) and -tax['amount']) or 0.0,
'tax_line_id': tax['id'],
- 'partner_id': partner_id
+ 'partner_id': partner_id,
+ 'order_id': order.id
})
# round tax lines per order
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index a23b478a61f..7984cd13c8f 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -480,7 +480,7 @@ class PurchaseOrder(models.Model):
'sequence': max(line.product_id.seller_ids.mapped('sequence')) + 1 if line.product_id.seller_ids else 1,
'product_uom': line.product_uom.id,
'min_qty': 0.0,
- 'price': self.currency_id.compute(line.price_unit, currency),
+ 'price': self.currency_id.compute(line.price_unit, currency, round=False),
'currency_id': currency.id,
'delay': 0,
}
diff --git a/addons/sale_stock/models/stock.py b/addons/sale_stock/models/stock.py
index eee0424021c..2a7f9e5560e 100644
--- a/addons/sale_stock/models/stock.py
+++ b/addons/sale_stock/models/stock.py
@@ -36,6 +36,15 @@ class StockMove(models.Model):
#rslt += invoices.mapped('refund_invoice_ids')
return rslt
+ def _assign_picking_post_process(self, new=False):
+ super(StockMove, self)._assign_picking_post_process(new=new)
+ if new and self.sale_line_id and self.sale_line_id.order_id:
+ self.picking_id.message_post_with_view(
+ 'mail.message_origin_link',
+ values={'self': self.picking_id, 'origin': self.sale_line_id.order_id},
+ subtype_id=self.env.ref('mail.mt_note').id)
+
+
class ProcurementGroup(models.Model):
_inherit = 'procurement.group'
diff --git a/addons/sales_team/models/crm_team.py b/addons/sales_team/models/crm_team.py
index f3708fc5b10..180e2879cdd 100644
--- a/addons/sales_team/models/crm_team.py
+++ b/addons/sales_team/models/crm_team.py
@@ -8,7 +8,7 @@ from dateutil.relativedelta import relativedelta
import json
from odoo import api, fields, models, _
-from odoo.exceptions import UserError
+from odoo.exceptions import AccessError, UserError
from odoo.release import version
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
@@ -33,8 +33,13 @@ class CrmTeam(models.Model):
team_id = self.env['crm.team'].browse(self.env.context.get('default_team_id'))
if not team_id:
default_team_id = self.env.ref('sales_team.team_sales_department', raise_if_not_found=False)
- if default_team_id and (self.env.context.get('default_type') != 'lead' or default_team_id.use_leads):
- team_id = default_team_id
+ if default_team_id:
+ try:
+ default_team_id.check_access_rule('read')
+ except AccessError:
+ return self.env['crm.team']
+ if self.env.context.get('default_type') != 'lead' or default_team_id.use_leads and default_team_id.active:
+ team_id = default_team_id
return team_id
def _get_default_favorite_user_ids(self):
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 88aede3ecf3..7f190e8d08a 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -716,7 +716,7 @@ class StockMove(models.Model):
recompute = True
picking = Picking.create(move._get_new_picking_values())
move.write({'picking_id': picking.id})
-
+ move._assign_picking_post_process(new=recompute)
# If this method is called in batch by a write on a one2many and
# at some point had to create a picking, some next iterations could
# try to find back the created picking. As we look for it by searching
@@ -726,6 +726,9 @@ class StockMove(models.Model):
move.recompute()
return True
+ def _assign_picking_post_process(self, new=False):
+ pass
+
def _get_new_picking_values(self):
""" Prepares a new picking for this move as it could not be assigned to
another picking. This method is designed to be inherited. """
diff --git a/addons/stock/models/stock_scrap.py b/addons/stock/models/stock_scrap.py
index bb985547ede..80fc6e0eabb 100644
--- a/addons/stock/models/stock_scrap.py
+++ b/addons/stock/models/stock_scrap.py
@@ -121,6 +121,8 @@ class StockScrap(models.Model):
def action_validate(self):
self.ensure_one()
+ if self.product_id.type != 'product':
+ return self.do_scrap()
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
available_qty = sum(self.env['stock.quant']._gather(self.product_id,
self.location_id,
diff --git a/addons/stock/report/report_deliveryslip.xml b/addons/stock/report/report_deliveryslip.xml
index 50289bee840..a5a9d7d0fe5 100644
--- a/addons/stock/report/report_deliveryslip.xml
+++ b/addons/stock/report/report_deliveryslip.xml
@@ -11,11 +11,11 @@
Customer Address:
-
+
+ |