[FIX] sale_mrp, sale_stock: _get_anglo_saxon_price_unit with a kit
When a kit is set with a valuation = "real_time" and invoice_policy = "delivery", the price unit of the kit computed for the Expense Account in the the journal of entries must be the sum of the average price of each component of this kit. Used case: Create a kit001 with: *General information: -Product Type=Stockable Product -Invoicing Policy=Delivered Quantities -Sale Price=100$ -Cost Price=80$ *Inventory: -Route=Manufacture -Internal Category=All/Sealable *its product category set with -Expense Account=210000 Cost of Goods Sold -Costing Method=Standard Price -Inventory Valuation=Perpetual(automated) *a bom of two components child001 and child002 Create child001 with: *General information: -Product Type=Stockable Product -Invoicing Policy=Delivered Quantities -Sale Price=100$ -Cost Price=70$ *Inventory: -Route=Buy -Internal Category=All *its product category set with -Expense Account=210000 Cost of Goods Sold -Costing Method=Real Price -Inventory Valuation=Perpetual(automated) Create child002 with: *General information: -Product Type=Stockable Product -Invoicing Policy=Delivered Quantities -Sale Price=100$ -Cost Price=10$ *Inventory: -Route=Buy -Internal Category=All *its product category set with -Expense Account=210000 Cost of Goods Sold -Costing Method=Real Price -Inventory Valuation=Perpetual(automated) Create a SO with: -one line with the kit001 Confirm the SO Validate the Delivery Create and view the invoices -per invoicable lines(deduct down payments) Validate the invoice Result: Before the fix: in the entry created for this invoice, the debit for the kit001 in Cost of Goods Sold was equal to 70 instead of 80. After the fix: the debit for the kit001 in Cost of Goods Sold was equal to 80(the sum of the average price of each component of the kit001). opw:674608
This commit is contained in:
@@ -60,6 +60,31 @@ class SaleOrderLine(models.Model):
|
||||
return 0.0
|
||||
return super(SaleOrderLine, self)._get_delivered_qty()
|
||||
|
||||
@api.multi
|
||||
def _get_bom_component_qty(self, bom):
|
||||
product_uom_qty_bom = self.env['product.uom']._compute_qty_obj(self.product_uom, self.product_uom_qty, bom.product_uom)
|
||||
bom_exploded = self.env['mrp.bom']._bom_explode(bom, self.product_id, product_uom_qty_bom)[0]
|
||||
components = {}
|
||||
for bom_line in bom_exploded:
|
||||
product = bom_line['product_id']
|
||||
uom = bom_line['product_uom']
|
||||
qty = bom_line['product_qty']
|
||||
if components.get(product, False):
|
||||
if uom != components[product]['uom']:
|
||||
from_uom_id = uom
|
||||
to_uom_id = components[product]['uom']
|
||||
qty = self.env['product.uom']._compute_qty(from_uom_id, qty, to_uom_id=to_uom_id)
|
||||
components[product]['qty'] += qty
|
||||
else:
|
||||
# To be in the uom reference of the product
|
||||
to_uom = self.env['product.product'].browse([product])[0].uom_id.id
|
||||
if uom != to_uom:
|
||||
qty = self.env['product.uom']._compute_qty(uom, qty, to_uom_id=to_uom)
|
||||
components[product] = {'qty': qty, 'uom': to_uom}
|
||||
return components
|
||||
|
||||
|
||||
|
||||
@api.multi
|
||||
def _prepare_order_line_procurement(self, group_id=False):
|
||||
vals = super(SaleOrderLine, self)._prepare_order_line_procurement(group_id=group_id)
|
||||
@@ -85,3 +110,40 @@ class StockMove(models.Model):
|
||||
"""
|
||||
property_ids = move.procurement_id.sale_line_id.property_ids.ids
|
||||
return super(StockMove, self.with_context(property_ids=property_ids))._action_explode(move)
|
||||
|
||||
class AccountInvoiceLine(models.Model):
|
||||
_inherit = "account.invoice.line"
|
||||
|
||||
def _get_anglo_saxon_price_unit(self):
|
||||
price_unit = super(AccountInvoiceLine, self)._get_anglo_saxon_price_unit()
|
||||
# in case of anglo saxon with a product configured as invoiced based on delivery, with perpetual
|
||||
# valuation and real price costing method, we must find the real price for the cost of good sold
|
||||
uom_obj = self.env['product.uom']
|
||||
if self.product_id.invoice_policy == "delivery":
|
||||
for s_line in self.sale_line_ids:
|
||||
# qtys already invoiced
|
||||
qty_done = sum([uom_obj._compute_qty_obj(x.uom_id, x.quantity, x.product_id.uom_id) for x in s_line.invoice_lines if x.invoice_id.state in ('open', 'paid')])
|
||||
quantity = uom_obj._compute_qty_obj(self.uom_id, self.quantity, self.product_id.uom_id)
|
||||
# Put moves in fixed order by date executed
|
||||
moves = self.env['stock.move']
|
||||
for procurement in s_line.procurement_ids:
|
||||
moves |= procurement.move_ids
|
||||
moves.sorted(lambda x: x.date)
|
||||
# Go through all the moves and do nothing until you get to qty_done
|
||||
# Beyond qty_done we need to calculate the average of the price_unit
|
||||
# on the moves we encounter.
|
||||
bom = s_line.product_id.product_tmpl_id.bom_ids and s_line.product_id.product_tmpl_id.bom_ids[0]
|
||||
if bom.type == 'phantom':
|
||||
average_price_unit = 0
|
||||
components = s_line._get_bom_component_qty(bom)
|
||||
for product_id in components.keys():
|
||||
factor = components[product_id]['qty']
|
||||
prod_moves = [m for m in moves if m.product_id.id == product_id]
|
||||
prod_qty_done = factor * qty_done
|
||||
prod_quantity = factor * quantity
|
||||
average_price_unit += factor * self._compute_average_price(prod_qty_done, prod_quantity, prod_moves)
|
||||
price_unit = average_price_unit or price_unit
|
||||
price_unit = uom_obj._compute_qty_obj(self.uom_id, price_unit, self.product_id.uom_id, round=False)
|
||||
return price_unit
|
||||
else:
|
||||
return price_unit
|
||||
|
||||
@@ -327,27 +327,31 @@ class AccountInvoiceLine(models.Model):
|
||||
# Go through all the moves and do nothing until you get to qty_done
|
||||
# Beyond qty_done we need to calculate the average of the price_unit
|
||||
# on the moves we encounter.
|
||||
average_price_unit = 0
|
||||
qty_delivered = 0
|
||||
invoiced_qty = 0
|
||||
for move in moves:
|
||||
if move.state != 'done':
|
||||
continue
|
||||
invoiced_qty += move.product_qty
|
||||
if invoiced_qty <= qty_done:
|
||||
continue
|
||||
qty_to_consider = move.product_qty
|
||||
if invoiced_qty - move.product_qty < qty_done:
|
||||
qty_to_consider = invoiced_qty - qty_done
|
||||
qty_to_consider = min(qty_to_consider, quantity - qty_delivered)
|
||||
qty_delivered += qty_to_consider
|
||||
average_price_unit = (average_price_unit * (qty_delivered - qty_to_consider) + move.price_unit * qty_to_consider) / qty_delivered
|
||||
if qty_delivered == quantity:
|
||||
break
|
||||
average_price_unit = self._compute_average_price(qty_done, quantity, moves)
|
||||
price_unit = average_price_unit or price_unit
|
||||
price_unit = uom_obj._compute_qty_obj(self.uom_id, price_unit, self.product_id.uom_id, round=False)
|
||||
return price_unit
|
||||
|
||||
def _compute_average_price(self, qty_done, quantity, moves):
|
||||
average_price_unit = 0
|
||||
qty_delivered = 0
|
||||
invoiced_qty = 0
|
||||
for move in moves:
|
||||
if move.state != 'done':
|
||||
continue
|
||||
invoiced_qty += move.product_qty
|
||||
if invoiced_qty <= qty_done:
|
||||
continue
|
||||
qty_to_consider = move.product_qty
|
||||
if invoiced_qty - move.product_qty < qty_done:
|
||||
qty_to_consider = invoiced_qty - qty_done
|
||||
qty_to_consider = min(qty_to_consider, quantity - qty_delivered)
|
||||
qty_delivered += qty_to_consider
|
||||
average_price_unit = (average_price_unit * (qty_delivered - qty_to_consider) + move.price_unit * qty_to_consider) / qty_delivered
|
||||
if qty_delivered == quantity:
|
||||
break
|
||||
return average_price_unit
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
Reference in New Issue
Block a user