From 2c55e091fad83666e938b13966f19fb3b13a2327 Mon Sep 17 00:00:00 2001 From: Martin Quinteros Date: Wed, 18 Sep 2024 12:24:58 -0300 Subject: [PATCH 1/4] [IMP] Add stock_currency_valuation module --- stock_currency_valuation/__init__.py | 2 + stock_currency_valuation/__manifest__.py | 26 ++++++ stock_currency_valuation/models/__init__.py | 5 ++ .../models/product_category.py | 12 +++ .../models/product_product.py | 18 ++++ .../models/product_template.py | 70 +++++++++++++++ stock_currency_valuation/models/stock_move.py | 87 +++++++++++++++++++ .../models/stock_valuation_layer.py | 35 ++++++++ stock_currency_valuation/views/product.xml | 18 ++++ .../views/product_category.xml | 13 +++ .../views/stock_valuation_layer.xml | 28 ++++++ stock_currency_valuation/wizard/__init__,py | 1 + .../stock_valuation_layer_revaluation.py | 22 +++++ 13 files changed, 337 insertions(+) create mode 100644 stock_currency_valuation/__init__.py create mode 100644 stock_currency_valuation/__manifest__.py create mode 100644 stock_currency_valuation/models/__init__.py create mode 100644 stock_currency_valuation/models/product_category.py create mode 100644 stock_currency_valuation/models/product_product.py create mode 100644 stock_currency_valuation/models/product_template.py create mode 100644 stock_currency_valuation/models/stock_move.py create mode 100644 stock_currency_valuation/models/stock_valuation_layer.py create mode 100644 stock_currency_valuation/views/product.xml create mode 100644 stock_currency_valuation/views/product_category.xml create mode 100644 stock_currency_valuation/views/stock_valuation_layer.xml create mode 100644 stock_currency_valuation/wizard/__init__,py create mode 100644 stock_currency_valuation/wizard/stock_valuation_layer_revaluation.py diff --git a/stock_currency_valuation/__init__.py b/stock_currency_valuation/__init__.py new file mode 100644 index 000000000..9b4296142 --- /dev/null +++ b/stock_currency_valuation/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/stock_currency_valuation/__manifest__.py b/stock_currency_valuation/__manifest__.py new file mode 100644 index 000000000..5efadb631 --- /dev/null +++ b/stock_currency_valuation/__manifest__.py @@ -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', +} diff --git a/stock_currency_valuation/models/__init__.py b/stock_currency_valuation/models/__init__.py new file mode 100644 index 000000000..1226f4470 --- /dev/null +++ b/stock_currency_valuation/models/__init__.py @@ -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 diff --git a/stock_currency_valuation/models/product_category.py b/stock_currency_valuation/models/product_category.py new file mode 100644 index 000000000..d76a9d4f3 --- /dev/null +++ b/stock_currency_valuation/models/product_category.py @@ -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, + ) diff --git a/stock_currency_valuation/models/product_product.py b/stock_currency_valuation/models/product_product.py new file mode 100644 index 000000000..80406efe0 --- /dev/null +++ b/stock_currency_valuation/models/product_product.py @@ -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) diff --git a/stock_currency_valuation/models/product_template.py b/stock_currency_valuation/models/product_template.py new file mode 100644 index 000000000..af8661dd1 --- /dev/null +++ b/stock_currency_valuation/models/product_template.py @@ -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)] diff --git a/stock_currency_valuation/models/stock_move.py b/stock_currency_valuation/models/stock_move.py new file mode 100644 index 000000000..9e5068350 --- /dev/null +++ b/stock_currency_valuation/models/stock_move.py @@ -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 diff --git a/stock_currency_valuation/models/stock_valuation_layer.py b/stock_currency_valuation/models/stock_valuation_layer.py new file mode 100644 index 000000000..b575e0ad5 --- /dev/null +++ b/stock_currency_valuation/models/stock_valuation_layer.py @@ -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 diff --git a/stock_currency_valuation/views/product.xml b/stock_currency_valuation/views/product.xml new file mode 100644 index 000000000..961300a89 --- /dev/null +++ b/stock_currency_valuation/views/product.xml @@ -0,0 +1,18 @@ + + + product.template.form + product.template + + + + + + + diff --git a/stock_currency_valuation/views/product_category.xml b/stock_currency_valuation/views/product_category.xml new file mode 100644 index 000000000..7f1cb1687 --- /dev/null +++ b/stock_currency_valuation/views/product_category.xml @@ -0,0 +1,13 @@ + + + product.category.form + product.category + + +
+ + + + +
+
diff --git a/stock_currency_valuation/views/stock_valuation_layer.xml b/stock_currency_valuation/views/stock_valuation_layer.xml new file mode 100644 index 000000000..d1da503ed --- /dev/null +++ b/stock_currency_valuation/views/stock_valuation_layer.xml @@ -0,0 +1,28 @@ + + + stock.valuation.layer.tree + stock.valuation.layer + + + + + + + + + + + stock.valuation.layer.form + stock.valuation.layer + + + + + + + + +
+
+
+
diff --git a/stock_currency_valuation/wizard/__init__,py b/stock_currency_valuation/wizard/__init__,py new file mode 100644 index 000000000..fc5fdb745 --- /dev/null +++ b/stock_currency_valuation/wizard/__init__,py @@ -0,0 +1 @@ +from . import stock_valuation_layer_revaluation diff --git a/stock_currency_valuation/wizard/stock_valuation_layer_revaluation.py b/stock_currency_valuation/wizard/stock_valuation_layer_revaluation.py new file mode 100644 index 000000000..57dc7e598 --- /dev/null +++ b/stock_currency_valuation/wizard/stock_valuation_layer_revaluation.py @@ -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 From ec4b4718f7f382dbc9c81bd01e571c3223cd6c0d Mon Sep 17 00:00:00 2001 From: Martin Quinteros Date: Tue, 26 Nov 2024 07:37:41 -0300 Subject: [PATCH 2/4] [IMP] Some changes --- stock_currency_valuation/__manifest__.py | 1 + stock_currency_valuation/models/__init__.py | 2 + .../models/account_move.py | 28 +++++++++++ .../models/product_template.py | 50 ++++++++----------- .../models/stock_landed_cost.py | 26 ++++++++++ stock_currency_valuation/models/stock_move.py | 3 +- .../models/stock_valuation_layer.py | 22 +++++--- .../wizard/{__init__,py => __init__.py} | 0 .../stock_valuation_layer_revaluation.py | 14 ++++-- 9 files changed, 106 insertions(+), 40 deletions(-) create mode 100644 stock_currency_valuation/models/account_move.py create mode 100644 stock_currency_valuation/models/stock_landed_cost.py rename stock_currency_valuation/wizard/{__init__,py => __init__.py} (100%) diff --git a/stock_currency_valuation/__manifest__.py b/stock_currency_valuation/__manifest__.py index 5efadb631..875043aa5 100644 --- a/stock_currency_valuation/__manifest__.py +++ b/stock_currency_valuation/__manifest__.py @@ -10,6 +10,7 @@ ], 'depends': [ 'stock_account', + 'stock_landed_costs', 'product_replenishment_cost', ], 'data': [ diff --git a/stock_currency_valuation/models/__init__.py b/stock_currency_valuation/models/__init__.py index 1226f4470..3aeac297f 100644 --- a/stock_currency_valuation/models/__init__.py +++ b/stock_currency_valuation/models/__init__.py @@ -3,3 +3,5 @@ from . import product_product from . import product_template from . import stock_move +from . import stock_landed_cost +from . import account_move diff --git a/stock_currency_valuation/models/account_move.py b/stock_currency_valuation/models/account_move.py new file mode 100644 index 000000000..12ff27089 --- /dev/null +++ b/stock_currency_valuation/models/account_move.py @@ -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 diff --git a/stock_currency_valuation/models/product_template.py b/stock_currency_valuation/models/product_template.py index af8661dd1..d07063688 100644 --- a/stock_currency_valuation/models/product_template.py +++ b/stock_currency_valuation/models/product_template.py @@ -19,46 +19,38 @@ class productTemplate(models.Model): ondelete={'average_in_currency': 'set default'} ) - @api.depends() + @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() - 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, - }) + 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): - # Depends on force_company context because standard_price_in_currency is company_dependent - # on the product_product + # 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 = 0.0 + 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: diff --git a/stock_currency_valuation/models/stock_landed_cost.py b/stock_currency_valuation/models/stock_landed_cost.py new file mode 100644 index 000000000..f6345ae32 --- /dev/null +++ b/stock_currency_valuation/models/stock_landed_cost.py @@ -0,0 +1,26 @@ +from odoo import models + + +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 + 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 diff --git a/stock_currency_valuation/models/stock_move.py b/stock_currency_valuation/models/stock_move.py index 9e5068350..ee16c8886 100644 --- a/stock_currency_valuation/models/stock_move.py +++ b/stock_currency_valuation/models/stock_move.py @@ -1,12 +1,13 @@ from collections import defaultdict -from odoo import models, fields +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) diff --git a/stock_currency_valuation/models/stock_valuation_layer.py b/stock_currency_valuation/models/stock_valuation_layer.py index b575e0ad5..7abad486c 100644 --- a/stock_currency_valuation/models/stock_valuation_layer.py +++ b/stock_currency_valuation/models/stock_valuation_layer.py @@ -1,7 +1,5 @@ -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 +from odoo import models, fields, api +from odoo.tools.float_utils import float_is_zero class StockValuationLayer(models.Model): @@ -17,8 +15,7 @@ class StockValuationLayer(models.Model): @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 - + # 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, @@ -33,3 +30,16 @@ def _compute_other_currency_values(self): else: rec.value_in_currency = False rec.unit_cost_in_currency = False + + @api.model_create_multi + def create(self, vals_list): + res = super().create(vals_list) + res._update_currency_standard_price() + return res + + def _update_currency_standard_price(self): + for layer_id in self.filtered(lambda x: x.stock_landed_cost_id and x.product_id.cost_method == 'average' and x.value_in_currency): + # batch standard price computation avoid recompute quantity_svl at each iteration + product = layer_id.product_id.with_company(layer_id.company_id) + if not float_is_zero(product.quantity_svl, precision_rounding=product.uom_id.rounding): + product.sudo().with_context(disable_auto_svl=True).standard_price_in_currency += layer_id.value_in_currency / product.quantity_svl diff --git a/stock_currency_valuation/wizard/__init__,py b/stock_currency_valuation/wizard/__init__.py similarity index 100% rename from stock_currency_valuation/wizard/__init__,py rename to stock_currency_valuation/wizard/__init__.py diff --git a/stock_currency_valuation/wizard/stock_valuation_layer_revaluation.py b/stock_currency_valuation/wizard/stock_valuation_layer_revaluation.py index 57dc7e598..eef1b970f 100644 --- a/stock_currency_valuation/wizard/stock_valuation_layer_revaluation.py +++ b/stock_currency_valuation/wizard/stock_valuation_layer_revaluation.py @@ -1,14 +1,15 @@ -from odoo import models +from odoo import models, fields, _ +from odoo.exceptions import UserError +from odoo.tools import float_compare, float_is_zero 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: + # Update the stardard price in currency in case of AVCO # 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. @@ -19,4 +20,9 @@ def action_validate_revaluation(self): 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 + res = super(StockValuationLayerRevaluation, self.with_context( + revaluation_force_currency=product_id.categ_id.valuation_currency_id, + revaluation_value_in_currency=value_in_currency + )).action_validate_revaluation() + return res + return super().action_validate_revaluation() From 2efecd35c4ccd582eab7ac5c0cf3decbac5c0c4a Mon Sep 17 00:00:00 2001 From: Martin Quinteros Date: Tue, 3 Dec 2024 08:19:20 -0300 Subject: [PATCH 3/4] [FIX] Fix fields declaration --- stock_currency_valuation/models/product_product.py | 5 +---- stock_currency_valuation/models/product_template.py | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/stock_currency_valuation/models/product_product.py b/stock_currency_valuation/models/product_product.py index 80406efe0..b3e786334 100644 --- a/stock_currency_valuation/models/product_product.py +++ b/stock_currency_valuation/models/product_product.py @@ -5,10 +5,7 @@ class productProduct(models.Model): _inherit = 'product.product' - valuation_currency_id = fields.Many2one( - related="categ_id.valuation_currency_id", - currency_field='valuation_currency_id' - ) + 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", diff --git a/stock_currency_valuation/models/product_template.py b/stock_currency_valuation/models/product_template.py index d07063688..54485435e 100644 --- a/stock_currency_valuation/models/product_template.py +++ b/stock_currency_valuation/models/product_template.py @@ -5,10 +5,7 @@ class productTemplate(models.Model): _inherit = 'product.template' - valuation_currency_id = fields.Many2one( - related="categ_id.valuation_currency_id", - currency_field='valuation_currency_id' - ) + 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', From bee596e16c9547630217595b1b7c0d1c581ed89c Mon Sep 17 00:00:00 2001 From: Martin Quinteros Date: Thu, 19 Dec 2024 16:38:10 -0300 Subject: [PATCH 4/4] [IMP] Add manual valuation in picking and landed --- stock_currency_valuation/__manifest__.py | 2 + stock_currency_valuation/models/__init__.py | 1 + .../models/stock_landed_cost.py | 55 ++++++++++++++++--- .../models/stock_picking.py | 39 +++++++++++++ .../models/stock_valuation_layer.py | 32 ++++++++--- .../views/stock_landed_cost_views.xml | 18 ++++++ .../views/stock_picking.xml | 19 +++++++ .../views/stock_valuation_layer.xml | 1 + 8 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 stock_currency_valuation/models/stock_picking.py create mode 100644 stock_currency_valuation/views/stock_landed_cost_views.xml create mode 100644 stock_currency_valuation/views/stock_picking.xml diff --git a/stock_currency_valuation/__manifest__.py b/stock_currency_valuation/__manifest__.py index 875043aa5..b48e7a966 100644 --- a/stock_currency_valuation/__manifest__.py +++ b/stock_currency_valuation/__manifest__.py @@ -15,6 +15,8 @@ ], 'data': [ 'views/product_category.xml', + 'views/stock_picking.xml', + 'views/stock_landed_cost_views.xml', 'views/product.xml', 'views/stock_valuation_layer.xml', ], diff --git a/stock_currency_valuation/models/__init__.py b/stock_currency_valuation/models/__init__.py index 3aeac297f..fb2b40227 100644 --- a/stock_currency_valuation/models/__init__.py +++ b/stock_currency_valuation/models/__init__.py @@ -5,3 +5,4 @@ from . import stock_move from . import stock_landed_cost from . import account_move +from . import stock_picking diff --git a/stock_currency_valuation/models/stock_landed_cost.py b/stock_currency_valuation/models/stock_landed_cost.py index f6345ae32..564df5cbe 100644 --- a/stock_currency_valuation/models/stock_landed_cost.py +++ b/stock_currency_valuation/models/stock_landed_cost.py @@ -1,6 +1,44 @@ -from odoo import models +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' @@ -9,12 +47,15 @@ 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 - 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, - ) + 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 diff --git a/stock_currency_valuation/models/stock_picking.py b/stock_currency_valuation/models/stock_picking.py new file mode 100644 index 000000000..9304eba4a --- /dev/null +++ b/stock_currency_valuation/models/stock_picking.py @@ -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 diff --git a/stock_currency_valuation/models/stock_valuation_layer.py b/stock_currency_valuation/models/stock_valuation_layer.py index 7abad486c..a314b443c 100644 --- a/stock_currency_valuation/models/stock_valuation_layer.py +++ b/stock_currency_valuation/models/stock_valuation_layer.py @@ -11,18 +11,36 @@ class StockValuationLayer(models.Model): 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() + manual_currency_rate = fields.Float( + store=True, + digits=0, + compute='_compute_manual_currency_rate' + ) - @api.depends('categ_id', 'value', 'bypass_currency_valuation', 'stock_valuation_layer_id') + @api.depends('stock_landed_cost_id', 'stock_move_id') + def _compute_manual_currency_rate(self): + for rec in self: + if rec.stock_landed_cost_id: + rec.manual_currency_rate = rec.stock_landed_cost_id.currency_rate + elif rec.stock_move_id: + rec.manual_currency_rate = rec.stock_move_id.picking_id.currency_rate + else: + rec.manual_currency_rate = False + + @api.depends('categ_id', 'value', 'bypass_currency_valuation', 'stock_valuation_layer_id', 'manual_currency_rate') 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.manual_currency_rate: + rec.value_in_currency = rec.value * rec.manual_currency_rate + else: + 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: diff --git a/stock_currency_valuation/views/stock_landed_cost_views.xml b/stock_currency_valuation/views/stock_landed_cost_views.xml new file mode 100644 index 000000000..714e945f9 --- /dev/null +++ b/stock_currency_valuation/views/stock_landed_cost_views.xml @@ -0,0 +1,18 @@ + + + + stock.landed.cost.form + stock.landed.cost + + + + + + + diff --git a/stock_currency_valuation/views/stock_picking.xml b/stock_currency_valuation/views/stock_picking.xml new file mode 100644 index 000000000..d10fbd0c1 --- /dev/null +++ b/stock_currency_valuation/views/stock_picking.xml @@ -0,0 +1,19 @@ + + + + + stock.picking.form.inherit + stock.picking + + + + + + + diff --git a/stock_currency_valuation/views/stock_valuation_layer.xml b/stock_currency_valuation/views/stock_valuation_layer.xml index d1da503ed..eb417678f 100644 --- a/stock_currency_valuation/views/stock_valuation_layer.xml +++ b/stock_currency_valuation/views/stock_valuation_layer.xml @@ -8,6 +8,7 @@ +