Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMP] Add stock_currency_valuation module #544

Open
wants to merge 4 commits into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
29 changes: 29 additions & 0 deletions stock_currency_valuation/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
'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',
'stock_landed_costs',
'product_replenishment_cost',
],
'data': [
'views/product_category.xml',
'views/stock_picking.xml',
'views/stock_landed_cost_views.xml',
'views/product.xml',
'views/stock_valuation_layer.xml',
],
'installable': True,
'auto_install': False,
'application': False,
'assets': {
},
'license': 'AGPL-3',
}
8 changes: 8 additions & 0 deletions stock_currency_valuation/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from . import product_category
from . import stock_valuation_layer
from . import product_product
from . import product_template
from . import stock_move
from . import stock_landed_cost
from . import account_move
from . import stock_picking
28 changes: 28 additions & 0 deletions stock_currency_valuation/models/account_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from collections import defaultdict

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


class AccountMove(models.Model):
_inherit = "account.move"

@api.model_create_multi
def create(self, vals_list):
if self.env.context.get('revaluation_force_currency'):
vals_list = self.alter_vals_for_revaluation_in_currency(vals_list)
res_ids = super(AccountMove, self).create(vals_list)
return res_ids

def alter_vals_for_revaluation_in_currency(self, vals_list):
valuation_currency_id = self.env.context.get('revaluation_force_currency').id
value_in_currency = self.env.context.get('revaluation_value_in_currency')
vals_list[0]['line_ids'][0][2].update(({
'currency_id': valuation_currency_id,
'amount_currency': abs(value_in_currency)
}))
vals_list[0]['line_ids'][1][2].update(({
'currency_id': valuation_currency_id,
'amount_currency': abs(value_in_currency) * -1
}))
return vals_list
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,
)
15 changes: 15 additions & 0 deletions stock_currency_valuation/models/product_product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from odoo import models, fields


class productProduct(models.Model):

_inherit = 'product.product'

valuation_currency_id = fields.Many2one(related="categ_id.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)
59 changes: 59 additions & 0 deletions stock_currency_valuation/models/product_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from odoo import models, fields, api


class productTemplate(models.Model):

_inherit = 'product.template'

valuation_currency_id = fields.Many2one(related="categ_id.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('standard_price_in_currency')
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()
company_id = self.env.company
for rec in use_average_in_currency:
product_currency = rec.currency_id
replenishment_cost_rule = rec.replenishment_cost_rule_id
replenishment_cost = rec.standard_price_in_currency
if replenishment_cost_rule:
replenishment_cost = replenishment_cost_rule.compute_rule(replenishment_cost, rec)
replenishment_base_cost_on_currency = rec.valuation_currency_id._convert(
from_amount=replenishment_cost,
to_currency=product_currency,
company=company_id,
date=fields.date.today(),
)
rec.update({
'replenishment_base_cost_on_currency': replenishment_base_cost_on_currency,
'replenishment_cost': replenishment_cost,
})

@api.depends_context('company')
@api.depends('product_variant_ids', 'product_variant_ids.standard_price')
def _compute_standard_price_in_currency(self):
# Por ahora hacemos esto porque replishment cost no es compatible al 100% con variantes
# obtenemos el precio del primer producto
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 = template.product_variant_ids[:1].standard_price_in_currency

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)]
67 changes: 67 additions & 0 deletions stock_currency_valuation/models/stock_landed_cost.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from odoo import models, fields, api


class StockLandedCost(models.Model):
_inherit = 'stock.landed.cost'


valuation_currency_id = fields.Many2one(
'res.currency',
string='Secondary Currency Valuation',
compute='_compute_valuation_currency_id',
help='If no rate is defined, the rate of the confirmation date is used.',
)
inverse_currency_rate = fields.Float(
string = "Cotizacion",
compute='_compute_inverse_currency_rate',
inverse='_inverse_currency_rate',
)
currency_rate = fields.Float(
digits=0,
copy=False,
)

@api.depends('currency_rate')
def _compute_inverse_currency_rate(self):
for rec in self:
rec.inverse_currency_rate = 1/rec.currency_rate if rec.currency_rate else 0

def _inverse_currency_rate(self):
for rec in self:
rec.currency_rate = 1/rec.inverse_currency_rate if rec.inverse_currency_rate else 0

@api.depends('picking_ids')
def _compute_valuation_currency_id(self):
for rec in self:
valuation_currency_id = rec.picking_ids.with_company(rec.company_id.id).mapped('valuation_currency_id')
if len(valuation_currency_id) == 1:
rec.valuation_currency_id = valuation_currency_id.id
else:
rec.valuation_currency_id = False

class AdjustmentLines(models.Model):

_inherit = 'stock.valuation.adjustment.lines'

def _create_accounting_entries(self, move, qty_out):
AccountMoveLine = super()._create_accounting_entries(move, qty_out)
if self.product_id.categ_id.valuation_currency_id:
amount = AccountMoveLine[0][2]['debit'] or AccountMoveLine[0][2]['credit'] * -1
if self.cost_id.currency_rate:
value_in_currency = amount * self.cost_id.currency_rate
else:
value_in_currency = self.cost_id.currency_id._convert(
from_amount=amount,
to_currency=self.product_id.categ_id.valuation_currency_id,
company=self.cost_id.company_id,
date=self.create_date,
)
AccountMoveLine[0][2].update(({
'currency_id': self.product_id.categ_id.valuation_currency_id.id,
'amount_currency': value_in_currency
}))
AccountMoveLine[1][2].update(({
'currency_id': self.product_id.categ_id.valuation_currency_id.id,
'amount_currency': value_in_currency * -1
}))
return AccountMoveLine
88 changes: 88 additions & 0 deletions stock_currency_valuation/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from collections import defaultdict

from odoo import models, api, 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
39 changes: 39 additions & 0 deletions stock_currency_valuation/models/stock_picking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from odoo import models, fields, api


class StockPicking(models.Model):
_inherit = 'stock.picking'

valuation_currency_id = fields.Many2one(
'res.currency',
string='Secondary Currency Valuation',
compute='_compute_valuation_currency_id',
)
inverse_currency_rate = fields.Float(
string = "Cotizacion",
compute='_compute_inverse_currency_rate',
inverse='_inverse_currency_rate',
help='If no rate is defined, the rate of the confirmation date is used.',
)
currency_rate = fields.Float(
digits=0,
copy=False,
help="If no rate is defined, the rate of the confirmation date is used.",
)

@api.depends('currency_rate')
def _compute_inverse_currency_rate(self):
for rec in self:
rec.inverse_currency_rate = 1/rec.currency_rate if rec.currency_rate else 0

def _inverse_currency_rate(self):
for rec in self:
rec.currency_rate = 1/rec.inverse_currency_rate if rec.inverse_currency_rate else 0

def _compute_valuation_currency_id(self):
for rec in self:
valuation_currency_id = rec.move_ids.with_company(rec.company_id.id).mapped('product_id.categ_id.valuation_currency_id')
if len(valuation_currency_id) == 1:
rec.valuation_currency_id = valuation_currency_id.id
else:
rec.valuation_currency_id = False
Loading