Initial commit of stock_landed_costs_average for 11.0

This commit is contained in:
Jared Kipe
2018-10-16 08:19:30 -07:00
parent 907971f384
commit 991cc17d30
6 changed files with 222 additions and 0 deletions

View File

@@ -0,0 +1 @@
from . import models

View File

@@ -0,0 +1,21 @@
{
'name': 'Landed Costs Average',
'summary': 'Use Landed Costs on Average Cost inventory.',
'version': '11.0.1.0.0',
'author': "Hibou Corp. <hello@hibou.io>",
'category': 'Warehouse',
'license': 'AGPL-3',
'complexity': 'expert',
'images': [],
'website': "https://hibou.io",
'description': """
""",
'depends': [
'stock_landed_costs',
],
'demo': [],
'data': [
],
'auto_install': False,
'installable': True,
}

View File

@@ -0,0 +1 @@
from . import stock_landed_cost

View File

@@ -0,0 +1,96 @@
from odoo import api, models, _
from odoo.exceptions import UserError
import logging
_logger = logging.getLogger(__name__)
class LandedCost(models.Model):
_inherit = 'stock.landed.cost'
def get_valuation_lines(self):
"""
Override for allowing Average value inventory.
:return: list of new line values
"""
lines = []
for move in self.mapped('picking_ids').mapped('move_lines'):
# Only allow for real time valuated products with 'average' or 'fifo' cost
if move.product_id.valuation != 'real_time' or move.product_id.cost_method not in ('fifo', 'average'):
continue
vals = {
'product_id': move.product_id.id,
'move_id': move.id,
'quantity': move.product_qty,
'former_cost': move.value,
'weight': move.product_id.weight * move.product_qty,
'volume': move.product_id.volume * move.product_qty
}
lines.append(vals)
if not lines and self.mapped('picking_ids'):
raise UserError(_('The selected picking does not contain any move that would be impacted by landed costs. Landed costs are only possible for products configured in real time valuation with real price costing method. Please make sure it is the case, or you selected the correct picking'))
return lines
@api.multi
def button_validate(self):
"""
Override to directly set new standard_price on product if average costed.
:return: True
"""
if any(cost.state != 'draft' for cost in self):
raise UserError(_('Only draft landed costs can be validated'))
if any(not cost.valuation_adjustment_lines for cost in self):
raise UserError(_('No valuation adjustments lines. You should maybe recompute the landed costs.'))
if not self._check_sum():
raise UserError(_('Cost and adjustments lines do not match. You should maybe recompute the landed costs.'))
for cost in self:
move = self.env['account.move']
move_vals = {
'journal_id': cost.account_journal_id.id,
'date': cost.date,
'ref': cost.name,
'line_ids': [],
}
for line in cost.valuation_adjustment_lines.filtered(lambda line: line.move_id):
# Prorate the value at what's still in stock
_logger.warn('(line.move_id.remaining_qty / line.move_id.product_qty) * line.additional_landed_cost')
_logger.warn('(%s / %s) * %s' % (line.move_id.remaining_qty, line.move_id.product_qty, line.additional_landed_cost))
cost_to_add = (line.move_id.remaining_qty / line.move_id.product_qty) * line.additional_landed_cost
_logger.warn('cost_to_add: ' + str(cost_to_add))
new_landed_cost_value = line.move_id.landed_cost_value + line.additional_landed_cost
line.move_id.write({
'landed_cost_value': new_landed_cost_value,
'value': line.move_id.value + cost_to_add,
'remaining_value': line.move_id.remaining_value + cost_to_add,
'price_unit': (line.move_id.value + new_landed_cost_value) / line.move_id.product_qty,
})
# `remaining_qty` is negative if the move is out and delivered products that were not
# in stock.
qty_out = 0
if line.move_id._is_in():
qty_out = line.move_id.product_qty - line.move_id.remaining_qty
elif line.move_id._is_out():
qty_out = line.move_id.product_qty
move_vals['line_ids'] += line._create_accounting_entries(move, qty_out)
# Need to set the standard price directly on the product.
if line.product_id.cost_method == 'average':
# From product.do_change_standard_price
quant_locs = self.env['stock.quant'].sudo().read_group([('product_id', '=', line.product_id.id)],
['location_id'], ['location_id'])
quant_loc_ids = [loc['location_id'][0] for loc in quant_locs]
locations = self.env['stock.location'].search(
[('usage', '=', 'internal'), ('company_id', '=', self.env.user.company_id.id),
('id', 'in', quant_loc_ids)])
qty_available = line.product_id.with_context(location=locations.ids).qty_available
total_cost = (qty_available * line.product_id.standard_price) + cost_to_add
line.product_id.write({'standard_price': total_cost / qty_available})
move = move.create(move_vals)
cost.write({'state': 'done', 'account_move_id': move.id})
move.post()
return True

View File

@@ -0,0 +1 @@
from . import test_stock_landed_cost

View File

@@ -0,0 +1,102 @@
from odoo.addons.stock_landed_costs.tests.test_stock_landed_costs_purchase import TestLandedCosts
class TestLandedCostsAverage(TestLandedCosts):
def setUp(self):
super(TestLandedCostsAverage, self).setUp()
self.product_refrigerator.cost_method = 'average'
self.product_oven.cost_method = 'average'
def test_00_landed_costs_on_incoming_shipment(self):
original_standard_price = self.product_refrigerator.standard_price
super(TestLandedCostsAverage, self).test_00_landed_costs_on_incoming_shipment()
self.assertTrue(original_standard_price != self.product_refrigerator.standard_price)
def test_01_landed_costs_simple_average(self):
self.assertEqual(self.product_refrigerator.standard_price, 1.0)
self.assertEqual(self.product_refrigerator.qty_available, 0.0)
picking_in = self.Picking.create({
'partner_id': self.supplier_id,
'picking_type_id': self.picking_type_in_id,
'location_id': self.supplier_location_id,
'location_dest_id': self.stock_location_id})
self.Move.create({
'name': self.product_refrigerator.name,
'product_id': self.product_refrigerator.id,
'product_uom_qty': 5,
'product_uom': self.product_refrigerator.uom_id.id,
'picking_id': picking_in.id,
'location_id': self.supplier_location_id,
'location_dest_id': self.stock_location_id})
picking_in.action_confirm()
res_dict = picking_in.button_validate()
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
wizard.process()
self.assertEqual(self.product_refrigerator.standard_price, 1.0)
self.assertEqual(self.product_refrigerator.qty_available, 5.0)
stock_landed_cost = self._create_landed_costs({
'equal_price_unit': 50,
'quantity_price_unit': 0,
'weight_price_unit': 0,
'volume_price_unit': 0}, picking_in)
stock_landed_cost.compute_landed_cost()
stock_landed_cost.button_validate()
account_entry = self.env['account.move.line'].read_group(
[('move_id', '=', stock_landed_cost.account_move_id.id)], ['debit', 'credit', 'move_id'], ['move_id'])[0]
self.assertEqual(account_entry['debit'], 50.0, 'Wrong Account Entry')
self.assertEqual(self.product_refrigerator.standard_price, 11.0)
def test_02_landed_costs_average(self):
self.assertEqual(self.product_refrigerator.standard_price, 1.0)
self.assertEqual(self.product_refrigerator.qty_available, 0.0)
picking_in = self.Picking.create({
'partner_id': self.supplier_id,
'picking_type_id': self.picking_type_in_id,
'location_id': self.supplier_location_id,
'location_dest_id': self.stock_location_id})
self.Move.create({
'name': self.product_refrigerator.name,
'product_id': self.product_refrigerator.id,
'product_uom_qty': 5,
'product_uom': self.product_refrigerator.uom_id.id,
'picking_id': picking_in.id,
'location_id': self.supplier_location_id,
'location_dest_id': self.stock_location_id})
picking_in.action_confirm()
res_dict = picking_in.button_validate()
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
wizard.process()
self.assertEqual(self.product_refrigerator.standard_price, 1.0)
self.assertEqual(self.product_refrigerator.qty_available, 5.0)
picking_out = self.Picking.create({
'partner_id': self.customer_id,
'picking_type_id': self.picking_type_out_id,
'location_id': self.stock_location_id,
'location_dest_id': self.customer_location_id})
self.Move.create({
'name': self.product_refrigerator.name,
'product_id': self.product_refrigerator.id,
'product_uom_qty': 2,
'product_uom': self.product_refrigerator.uom_id.id,
'picking_id': picking_out.id,
'location_id': self.stock_location_id,
'location_dest_id': self.customer_location_id})
picking_out.action_confirm()
picking_out.action_assign()
res_dict = picking_out.button_validate()
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
wizard.process()
self.assertEqual(self.product_refrigerator.standard_price, 1.0)
self.assertEqual(self.product_refrigerator.qty_available, 3.0)
stock_landed_cost = self._create_landed_costs({
'equal_price_unit': 50,
'quantity_price_unit': 0,
'weight_price_unit': 0,
'volume_price_unit': 0}, picking_in)
stock_landed_cost.compute_landed_cost()
stock_landed_cost.button_validate()
self.assertEqual(self.product_refrigerator.standard_price, 11.0)