Skip to content

Commit

Permalink
[IMP] Add stock_currency_valuation module
Browse files Browse the repository at this point in the history
  • Loading branch information
maq-adhoc committed Nov 26, 2024
1 parent d31fffa commit 67a37d8
Show file tree
Hide file tree
Showing 13 changed files with 337 additions and 0 deletions.
2 changes: 2 additions & 0 deletions stock_currency_valuation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import wizard
26 changes: 26 additions & 0 deletions stock_currency_valuation/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
'name': 'Stock currency valuation',
'version': "16.0.1.0.0",
'category': 'Warehouse Management',
'sequence': 14,
'summary': '',
'author': 'ADHOC SA',
'website': 'www.adhoc.com.ar',
'images': [
],
'depends': [
'stock_account',
'product_replenishment_cost',
],
'data': [
'views/product_category.xml',
'views/product.xml',
'views/stock_valuation_layer.xml',
],
'installable': True,
'auto_install': False,
'application': False,
'assets': {
},
'license': 'AGPL-3',
}
5 changes: 5 additions & 0 deletions stock_currency_valuation/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import product_category
from . import stock_valuation_layer
from . import product_product
from . import product_template
from . import stock_move
12 changes: 12 additions & 0 deletions stock_currency_valuation/models/product_category.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import models, fields


class productCategory(models.Model):

_inherit = 'product.category'

valuation_currency_id = fields.Many2one(
'res.currency',
string='Secondary Currency Valuation',
company_dependent=True,
)
18 changes: 18 additions & 0 deletions stock_currency_valuation/models/product_product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from odoo import models, fields


class productProduct(models.Model):

_inherit = 'product.product'

valuation_currency_id = fields.Many2one(
related="categ_id.valuation_currency_id",
currency_field='valuation_currency_id'
)
standard_price_in_currency = fields.Float(
'Cost in currency', company_dependent=True,
groups="base.group_user",
)

def _change_standard_price(self, new_price):
super(productProduct, self.with_context(default_bypass_currency_valuation=True))._change_standard_price(new_price)
70 changes: 70 additions & 0 deletions stock_currency_valuation/models/product_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from odoo import models, fields, api


class productTemplate(models.Model):

_inherit = 'product.template'

valuation_currency_id = fields.Many2one(
related="categ_id.valuation_currency_id",
currency_field='valuation_currency_id'
)
standard_price_in_currency = fields.Float(
'Cost', compute='_compute_standard_price_in_currency',
inverse='_set_standard_price_in_currency', search='_search_standard_price_in_currency',
digits='Product Price', groups="base.group_user",
)
replenishment_cost_type = fields.Selection(
selection_add=[('average_in_currency', 'Average Cost in Currency')],
ondelete={'average_in_currency': 'set default'}
)

@api.depends()
def _compute_replenishment_cost(self):
use_average_in_currency = self.filtered(lambda x: x.replenishment_cost_type == "average_in_currency")
super(productTemplate, self - use_average_in_currency)._compute_replenishment_cost()
if use_average_in_currency:
company_id = self.env.company
# domain = [
# ('product_tmpl_id', 'in', use_average_in_currency.ids),
# ('company_id', '=', company_id.id),
# ]
# groups = self.env['stock.valuation.layer']._read_group(domain, ['value_in_currency:sum', 'quantity:sum'],
# ['product_tmpl_id'])
# products_avg_cost = {group['product_tmpl_id'][0]: group['value_in_currency'] / group['quantity']
# if group['quantity'] else 0 for group in groups}
for rec in use_average_in_currency:
product_currency = rec.currency_id
#product_cost = products_avg_cost.get(rec.id) or 0
price_unit = rec.valuation_currency_id._convert(
from_amount=rec.standard_price_in_currency,
to_currency=product_currency,
company=company_id,
date=fields.date.today(),
)
rec.update({
'replenishment_base_cost_currency_id': rec.valuation_currency_id.id,
'replenishment_base_cost_on_currency': price_unit,
'replenishment_cost': price_unit,
'replenishment_base_cost': rec.standard_price_in_currency,
})

@api.depends_context('company')
@api.depends('product_variant_ids', 'product_variant_ids.standard_price')
def _compute_standard_price_in_currency(self):
# Depends on force_company context because standard_price_in_currency is company_dependent
# on the product_product
unique_variants = self.filtered(lambda template: len(template.product_variant_ids) == 1)
for template in unique_variants:
template.standard_price_in_currency = template.product_variant_ids.standard_price_in_currency
for template in (self - unique_variants):
template.standard_price_in_currency = 0.0

def _set_standard_price_in_currency(self):
for template in self:
if len(template.product_variant_ids) == 1:
template.product_variant_ids.standard_price_in_currency = template.standard_price_in_currency

def _search_standard_price_in_currency(self, operator, value):
products = self.env['product.product'].search([('standard_price_in_currency', operator, value)], limit=None)
return [('id', 'in', products.mapped('product_tmpl_id').ids)]
87 changes: 87 additions & 0 deletions stock_currency_valuation/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from collections import defaultdict

from odoo import models, fields
from odoo.tools.float_utils import float_compare, float_is_zero


class StockMove(models.Model):
_inherit = "stock.move"

def _account_entry_move(self, qty, description, svl_id, cost):
am_vals_list = super()._account_entry_move(qty, description, svl_id, cost)
layer = self.env['stock.valuation.layer'].browse(svl_id)
if layer.valuation_currency_id:
for am_vals in am_vals_list:
for line_id in am_vals['line_ids']:
sign = -1 if line_id[2]['balance'] < 0 else 1
line_id[2].update({
'currency_id': layer.valuation_currency_id.id,
'amount_currency': abs(layer.value_in_currency) * sign
})
return am_vals_list

def product_price_update_before_done(self, forced_qty=None):
super().product_price_update_before_done(forced_qty=forced_qty)
# Actualizo tambien el costo en moneda
tmpl_dict = defaultdict(lambda: 0.0)
# adapt standard price on incomming moves if the product cost_method is 'average'
std_price_update = {}
for move in self.filtered(lambda move: move._is_in() and move.with_company(move.company_id).product_id.categ_id.valuation_currency_id
and move.with_company(move.company_id).product_id.cost_method == 'average'):
product_tot_qty_available = move.product_id.sudo().with_company(move.company_id).quantity_svl + tmpl_dict[move.product_id.id]
rounding = move.product_id.uom_id.rounding

valued_move_lines = move._get_in_move_lines()
qty_done = 0
for valued_move_line in valued_move_lines:
qty_done += valued_move_line.product_uom_id._compute_quantity(valued_move_line.qty_done, move.product_id.uom_id)

qty = forced_qty or qty_done
if float_is_zero(product_tot_qty_available, precision_rounding=rounding):
new_std_price_in_currency = move._get_currency_price_unit()
elif float_is_zero(product_tot_qty_available + move.product_qty, precision_rounding=rounding) or \
float_is_zero(product_tot_qty_available + qty, precision_rounding=rounding):
new_std_price_in_currency = move._get_currency_price_unit()
else:
# Get the standard price
amount_unit = std_price_update.get((move.company_id.id, move.product_id.id)) or move.product_id.with_company(move.company_id).standard_price_in_currency
new_std_price_in_currency = ((amount_unit * product_tot_qty_available) + (move._get_currency_price_unit() * qty)) / (product_tot_qty_available + qty)

tmpl_dict[move.product_id.id] += qty_done
# Write the standard price, as SUPERUSER_ID because a warehouse manager may not have the right to write on products
move.product_id.with_company(move.company_id.id).with_context(disable_auto_svl=True).sudo().write({'standard_price_in_currency': new_std_price_in_currency})

std_price_update[move.company_id.id, move.product_id.id] = new_std_price_in_currency
# adapt standard price on incomming moves if the product cost_method is 'fifo'
for move in self.filtered(lambda move:
move.with_company(move.company_id).product_id.cost_method == 'fifo'
and float_is_zero(move.product_id.sudo().quantity_svl, precision_rounding=move.product_id.uom_id.rounding)):
move.product_id.with_company(move.company_id.id).sudo().write({'standard_price_in_currency': move._get_currency_price_unit()})

def _get_currency_price_unit(self):
""" Returns the unit price from this stock move """
self.ensure_one()
currency_id = self.company_id.currency_id
if hasattr(self, 'purchase_order_Line') and self.purchase_order_Line:
currency_id = self.purchase_order_Line.currency_id
if hasattr(self, 'sale_line_id') and self.sale_line_id:
currency_id = self.sale_line_id.currency_id

price_unit = currency_id._convert(
from_amount=self.price_unit,
to_currency=self.product_id.categ_id.valuation_currency_id,
company=self.company_id,
date=fields.date.today(),
)
precision = self.env['decimal.precision'].precision_get('Product Price')
# If the move is a return, use the original move's price unit.
if self.origin_returned_move_id and self.origin_returned_move_id.sudo().stock_valuation_layer_ids:
layers = self.origin_returned_move_id.sudo().stock_valuation_layer_ids
# dropshipping create additional positive svl to make sure there is no impact on the stock valuation
# We need to remove them from the computation of the price unit.
if self.origin_returned_move_id._is_dropshipped() or self.origin_returned_move_id._is_dropshipped_returned():
layers = layers.filtered(lambda l: float_compare(l.value, 0, precision_rounding=l.product_id.uom_id.rounding) <= 0)
layers |= layers.stock_valuation_layer_ids
quantity = sum(layers.mapped("quantity"))
return sum(layers.mapped("value_in_currency")) / quantity if not float_is_zero(quantity, precision_rounding=layers.uom_id.rounding) else 0
return price_unit if not float_is_zero(price_unit, precision) or self._should_force_price_unit() else self.product_id.standard_price_in_currency
35 changes: 35 additions & 0 deletions stock_currency_valuation/models/stock_valuation_layer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from odoo import models, fields, api, _
from odoo.tools.safe_eval import safe_eval
from odoo.exceptions import ValidationError
from odoo.tools import float_compare


class StockValuationLayer(models.Model):

_inherit = 'stock.valuation.layer'

valuation_currency_id = fields.Many2one(related="categ_id.valuation_currency_id")
unit_cost_in_currency = fields.Monetary('Unit Value in currency', compute="_compute_other_currency_values", currency_field='valuation_currency_id', store=True)
value_in_currency = fields.Monetary('Total Value incurrency', compute="_compute_other_currency_values", currency_field='valuation_currency_id', store=True)
product_tmpl_id = fields.Many2one(store=True)
bypass_currency_valuation = fields.Boolean()

@api.depends('categ_id', 'value', 'bypass_currency_valuation', 'stock_valuation_layer_id')
def _compute_other_currency_values(self):
for rec in self:
##rec.stock_valuation_layer_id

if not rec.bypass_currency_valuation and rec.valuation_currency_id:
rec.value_in_currency = rec.currency_id._convert(
from_amount=rec.value,
to_currency=rec.valuation_currency_id,
company=rec.company_id,
date=rec.create_date,
)
if rec.quantity:
rec.unit_cost_in_currency = abs(rec.value_in_currency / rec.quantity)
else:
rec.unit_cost_in_currency = False
else:
rec.value_in_currency = False
rec.unit_cost_in_currency = False
18 changes: 18 additions & 0 deletions stock_currency_valuation/views/product.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<odoo>
<record id="product_template_form_view" model="ir.ui.view">
<field name="name">product.template.form</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product_replenishment_cost.product_template_form_view"/>
<field name="arch" type="xml">
<group name="accounting_cost">
<label for="standard_price_in_currency"/>
<div name="standard_price_uom">
<field name="standard_price_in_currency" class="oe_inline" widget="monetary" options="{'currency_field': 'valuation_currency_id', 'field_digits': True}"/>
<span groups="uom.group_uom">per
<field name="uom_name" class="oe_inline"/>
</span>
</div>
</group>
</field>
</record>
</odoo>
13 changes: 13 additions & 0 deletions stock_currency_valuation/views/product_category.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<odoo>
<record id="product_category_form_view" model="ir.ui.view">
<field name="name">product.category.form</field>
<field name="model">product.category</field>
<field name="inherit_id" ref="product.product_category_form_view"/>
<field name="arch" type="xml">
<form><field name="property_valuation" invisible="1"/></form>
<field name="property_valuation" position="after">
<field name="valuation_currency_id" attrs="{'invisible':[('property_cost_method', 'not in', ['average', 'fifo']), ('property_valuation', '!=', 'real_time')]}"/>
</field>
</field>
</record>
</odoo>
28 changes: 28 additions & 0 deletions stock_currency_valuation/views/stock_valuation_layer.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<odoo>
<record id="stock_valuation_layer_tree" model="ir.ui.view">
<field name="name">stock.valuation.layer.tree</field>
<field name="model">stock.valuation.layer</field>
<field name="inherit_id" ref="stock_account.stock_valuation_layer_tree"/>
<field name="arch" type="xml">
<field name="value" position="after">
<field name="valuation_currency_id" invisible="1"/>
<field name="unit_cost_in_currency" optional="hide"/>
<field name="value_in_currency" sum="Total in currency" optional="show"/>
</field>
</field>
</record>
<record id="stock_valuation_layer_form" model="ir.ui.view">
<field name="name">stock.valuation.layer.form</field>
<field name="model">stock.valuation.layer</field>
<field name="inherit_id" ref="stock_account.stock_valuation_layer_form"/>
<field name="arch" type="xml">
<field name="unit_cost" position="after">
<field name="unit_cost_in_currency"/>
</field>
<field name="value" position="after">
<field name="value_in_currency"/>
</field>
<form><field name="valuation_currency_id" invisible="1"/></form>
</field>
</record>
</odoo>
1 change: 1 addition & 0 deletions stock_currency_valuation/wizard/__init__,py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import stock_valuation_layer_revaluation
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from odoo import models


class StockValuationLayerRevaluation(models.TransientModel):
_inherit = 'stock.valuation.layer.revaluation'

def action_validate_revaluation(self):
res = super().action_validate_revaluation()
# Update the stardard price in currency in case of AVCO
product_id = self.product_id.with_company(self.company_id)
if product_id.categ_id.property_cost_method in ('average', 'fifo') and product_id.categ_id.valuation_currency_id:
# Para actualizar el costo en currency vuelvo a calcular el valor en moneda
# Si bien hago dos veces el calculo (en el layer y aqui) esto es mas
# sencillo que obtener el ultimo layer y agregar el valor.
value_in_currency = self.currency_id._convert(
from_amount=self.added_value,
to_currency=product_id.categ_id.valuation_currency_id,
company=self.company_id,
date=self.create_date,
)
product_id.with_context(disable_auto_svl=True).standard_price_in_currency += value_in_currency / self.current_quantity_svl
return res

0 comments on commit 67a37d8

Please sign in to comment.