-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ADD] Added osi_fifo_serialized_fix for Cab
- Loading branch information
1 parent
ba4fadf
commit bf5fbe2
Showing
7 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg | ||
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html | ||
:alt: License: LGPL-3 | ||
|
||
=============================== | ||
OSI FIFO Serialized Product Fix | ||
=============================== | ||
|
||
Properly handle Inventory Valuation | ||
with Serialized and Lot Tracked products | ||
This will work for MO and SO. | ||
|
||
Usage | ||
===== | ||
|
||
Properly handle Inventory Valuation | ||
with Serialized and Lot Tracked products | ||
|
||
Credits | ||
======= | ||
|
||
* Open Source Integrators <[email protected]> | ||
* Freni Patel <[email protected]> | ||
* Chankya Soni<[email protected]> | ||
|
||
Contributors | ||
------------ | ||
|
||
* Open Source Integrators <[email protected]> | ||
* Serpent Consulting Services Pvt. Ltd. <[email protected]> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
|
||
# Copyright (C) 2021, Open Source Integrators | ||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). | ||
|
||
{ | ||
"name": "OSI FIFO Serialized Product Fix", | ||
"summary": """Properly handle Inventory Valuation | ||
with Serialized and Lot Tracked products""", | ||
"version": "16.0.0.0.1", | ||
"license": "LGPL-3", | ||
"author": "Open Source Integrators", | ||
"website": "https://github.com/ursais/osi-addons", | ||
"category": "Accounting", | ||
"depends": ["stock_account", "mrp_account"], | ||
"data": ["views/stock_valuation_layer.xml"], | ||
"maintainers": ["osi-scampbell","Chanakya-OSI"], | ||
"installable": True, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Copyright (C) 2023, Open Source Integrators | ||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). | ||
|
||
from . import stock_move | ||
from . import stock_valuation_layer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
# Copyright (C) 2023, Open Source Integrators | ||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). | ||
|
||
from odoo import _, models | ||
from odoo.exceptions import UserError | ||
from odoo.tools import float_is_zero, float_repr, float_round, float_compare | ||
|
||
|
||
class StockMove(models.Model): | ||
_inherit = "stock.move" | ||
|
||
def get_ji_ids(self, move, svl_ids): | ||
ji_ids = self.env["account.move.line"].search( | ||
[ | ||
("name", "ilike", move.picking_id.name), | ||
("name", "ilike", move.product_id.name), | ||
] | ||
) | ||
if ji_ids: | ||
if len(svl_ids) > 1: | ||
final_layers = [] | ||
# If there are multiple layers, delete them and create correct ones | ||
for layer in move.stock_valuation_layer_ids: | ||
layer.sudo().unlink() | ||
for svl_id in svl_ids: | ||
val = move.product_id._prepare_out_svl_vals( | ||
svl_id[1], self.env.user.company_id | ||
) | ||
val_obj = self.env["stock.valuation.layer"].browse(svl_id[0]) | ||
val["unit_cost"] = val_obj.unit_cost | ||
val["company_id"] = self.env.user.company_id.id | ||
val["lot_ids"] = [(6, 0, svl_id[2])] | ||
val["account_move_id"] = ji_ids[0].move_id.id | ||
# The qty of serial products is always 1, lots could be > 1 | ||
if svl_id[3].product_id.tracking == "lot": | ||
val["quantity"] = svl_id[3].qty_done * -1 | ||
val["value"] = val_obj.unit_cost * val["quantity"] | ||
final_layers.append(val) | ||
final_layers = self.env["stock.valuation.layer"].create(final_layers) | ||
move.write({"stock_valuation_layer_ids": [(6, 0, final_layers.ids)]}) | ||
ji_ids[0].move_id.button_draft() | ||
ji_val = 0 | ||
for sv_id in move.stock_valuation_layer_ids: | ||
if sv_id.product_id == ji_ids[0].product_id: | ||
ji_val += sv_id.value * -1 | ||
# The Valuation Layers have been made, now edit the STJ Entries | ||
for ji_id in ji_ids: | ||
if ji_id.credit != 0: | ||
ji_id.with_context(check_move_validity=False).write( | ||
{"credit": ji_val} | ||
) | ||
else: | ||
ji_id.with_context(check_move_validity=False).write( | ||
{"debit": ji_val} | ||
) | ||
ji_ids[0].move_id.action_post() | ||
elif len(svl_ids) == 1: | ||
# Only 1 Valuation Layer, we can just change values | ||
if len(move.stock_valuation_layer_ids.ids) > 1: | ||
svl = self.env["stock.valuation.layer"].browse(svl_ids[0][0]) | ||
val = move.stock_valuation_layer_ids[0] | ||
val.unit_cost = -1 * svl.unit_cost | ||
for layer in move.stock_valuation_layer_ids: | ||
layer.sudo().unlink() | ||
move.write({"stock_valuation_layer_ids": [6, 0, val.id]}) | ||
else: | ||
move.stock_valuation_layer_ids.lot_ids = [(6, 0, svl_ids[0][2])] | ||
svl = self.env["stock.valuation.layer"].browse(svl_ids[0][0]) | ||
linked_svl = self.env["stock.valuation.layer"].search([('stock_valuation_layer_id', '=', svl.id)]) | ||
unit_cost = svl.unit_cost | ||
for link_svl in linked_svl: | ||
unit_cost += link_svl.value | ||
move.stock_valuation_layer_ids.unit_cost = unit_cost | ||
move.stock_valuation_layer_ids.value = (move.stock_valuation_layer_ids.quantity * move.stock_valuation_layer_ids.unit_cost) | ||
move.stock_valuation_layer_ids.remaining_value = move.stock_valuation_layer_ids.value | ||
ji_ids[0].move_id.button_draft() | ||
# The Valuation Layer has been changed, | ||
# now we have to edit the STJ Entry | ||
for ji_id in ji_ids: | ||
amount = ( | ||
move.stock_valuation_layer_ids.unit_cost | ||
# svl.unit_cost | ||
* move.stock_valuation_layer_ids.stock_move_id.product_uom_qty | ||
) | ||
if ji_id.credit != 0: | ||
ji_id.with_context(check_move_validity=False).write( | ||
{"credit": amount} | ||
) | ||
else: | ||
ji_id.with_context(check_move_validity=False).write( | ||
{"debit": amount} | ||
) | ||
ji_ids[0].move_id.action_post() | ||
|
||
def get_svl_ids(self, move): | ||
svl_ids = [] | ||
# We need to get all of the valuation layers associated with this product | ||
test_vals = self.env["stock.valuation.layer"].search( | ||
[("product_id", "=", move.product_id.id), ('stock_valuation_layer_id', '=', False)], order="id desc" | ||
) | ||
for line_id in move.move_line_ids: | ||
for valuation in test_vals: | ||
# Filter so we only have the Layers we got from Incoming Moves | ||
if line_id.lot_id in valuation.lot_ids and valuation.value > 0: | ||
if not self.check_found_vals(valuation.id, svl_ids): | ||
svl_ids.append( | ||
( | ||
valuation.id, | ||
1, | ||
[line_id.lot_id.id], | ||
line_id, | ||
) | ||
) | ||
break | ||
else: | ||
self.increment_qty(valuation.id, svl_ids, line_id.lot_id.id) | ||
if len(svl_ids) == 0 and self.location_id.create_je is True: | ||
raise UserError( | ||
_( | ||
"Need Check Stock Valution Layer For this Product :- %s" | ||
% (move.product_id.name) | ||
) | ||
) | ||
self.get_ji_ids(move, svl_ids) | ||
|
||
def _action_done(self, cancel_backorder=False): | ||
res = super()._action_done(cancel_backorder) | ||
for move in self: | ||
# Only run code on outgoing moves with serial or lot products | ||
if ( | ||
move.product_id.tracking in ["serial", "lot"] | ||
and move.picking_id.picking_type_id.code == "outgoing" | ||
): | ||
self.get_svl_ids(move) | ||
return res | ||
|
||
def increment_qty(self, value_id, svl_ids, lot_id): | ||
index = 0 | ||
for svl_id in svl_ids: | ||
if svl_id[0] == value_id: | ||
if lot_id not in svl_id[2]: | ||
svl_id[2].append(lot_id) | ||
svl_ids[index] = (svl_id[0], svl_id[1] + 1, svl_id[2]) | ||
index += 1 | ||
|
||
def check_found_vals(self, value_id, svl_ids): | ||
for svl_id in svl_ids: | ||
if value_id == svl_id[0]: | ||
return True | ||
return False | ||
|
||
# Tag Incoming Valuation Layers with their lot_ids | ||
def _create_in_svl(self, forced_quantity=None): | ||
res = super()._create_in_svl(forced_quantity) | ||
for move in self: | ||
for layer in res: | ||
if layer.stock_move_id.id == move.id: | ||
layer.lot_ids = [(6, 0, move.lot_ids.ids)] | ||
return res |
125 changes: 125 additions & 0 deletions
125
osi_fifo_serialized_fix/models/stock_valuation_layer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
# Copyright (C) 2023, Open Source Integrators | ||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). | ||
|
||
from odoo import fields, models, api | ||
from odoo.tools import float_is_zero, float_round | ||
|
||
|
||
class StockProductionLot(models.Model): | ||
_inherit = "stock.lot" | ||
|
||
real_price = fields.Float() | ||
|
||
|
||
class StockValuationLayer(models.Model): | ||
_inherit = "stock.valuation.layer" | ||
|
||
lot_ids = fields.Many2many("stock.lot", string="Lot ID's", readonly=True) | ||
repair_type = fields.Selection([('add', 'ADD'), ('remove', 'Remove')], string="Repair Type") | ||
|
||
@api.model | ||
def create(self, vals): | ||
res = super().create(vals) | ||
for layer in res.filtered(lambda l:l.stock_move_id.lot_ids or l.stock_valuation_layer_id.lot_ids): | ||
layer.lot_ids = [(6, 0, layer.stock_move_id.lot_ids.ids)] | ||
|
||
#Below code is only for Cabintouch as they are using pre-component / pick component location as a Production location | ||
if layer.stock_move_id.mapped('move_dest_ids').mapped('raw_material_production_id'): | ||
total_lot_price = 0 | ||
for lot in layer.lot_ids: | ||
all_lot_layers = self.search([('lot_ids', 'in', lot.id), ('id', '!=', layer.id)]) | ||
total_qty = sum([svl.quantity for svl in all_lot_layers]) | ||
total_value = sum([svl.value for svl in all_lot_layers]) | ||
svl_value = total_value / total_qty if total_qty else 1.0 | ||
lot.write({'real_price':svl_value}) | ||
total_lot_price+=svl_value | ||
layer.value=-total_lot_price | ||
layer.unit_cost = -(total_lot_price / layer.quantity if layer.quantity else 1) | ||
layer.remaining_value = layer.value | ||
layer.lot_ids = [(6, 0, [])] | ||
#=================================================================================== | ||
#Update the lot price when we do incoming lots | ||
#update the lot_ids for linked layer. | ||
#i.g. Landed cost layers, Price diff layers. | ||
if layer.stock_valuation_layer_id and not layer.lot_ids: | ||
layer.lot_ids = layer.stock_valuation_layer_id.lot_ids | ||
if layer.stock_move_id.location_id.usage in ('inventory', 'supplier'): | ||
layer_ids = self.search([('lot_ids','in',layer.lot_ids.ids)]) | ||
total_qty = sum(layer_ids.mapped("quantity")) | ||
total_value = sum(layer_ids.mapped("value")) | ||
layer.lot_ids.write({'real_price': total_value/total_qty if total_qty else 1}) | ||
|
||
#Update the lot price for the raw material moves. | ||
#Update the layer values for raw materual moves | ||
if layer.stock_move_id.raw_material_production_id: | ||
total_lot_price = 0 | ||
for lot in layer.lot_ids: | ||
all_lot_layers = self.search([('lot_ids', 'in', lot.id), ('id', '!=', layer.id)]) | ||
# all_moves = all_lot_layers.mapped('stock_move_id') or self.env['stock.move'] | ||
# rawmaterial_moves = all_lot_layers.mapped('stock_move_id').mapped('move_dest_ids').filtered(lambda l : l.raw_material_production_id) or self.env['stock.move'] | ||
# required_moves = all_moves - rawmaterial_moves | ||
|
||
# required_layers = self.search([('stock_move_id','in',required_moves.ids)]) | ||
total_qty = sum([svl.quantity for svl in all_lot_layers]) | ||
total_value = sum([svl.value for svl in all_lot_layers]) | ||
svl_value = total_value / total_qty if total_qty else 1.0 | ||
lot.write({'real_price':svl_value}) | ||
total_lot_price+=svl_value | ||
layer.value=-total_lot_price | ||
layer.unit_cost = -(total_lot_price / layer.quantity if layer.quantity else 1) | ||
layer.remaining_value = layer.value | ||
|
||
if layer.stock_move_id.production_id and layer.stock_move_id.product_id.cost_method in ('fifo', 'average'): | ||
layer.stock_move_id.production_id.lot_producing_id.real_price = layer.stock_move_id.price_unit | ||
|
||
#Update the lot price for the FG moves. | ||
#Update the layer values for FG moves | ||
# if layer.stock_move_id.production_id: | ||
# production = layer.stock_move_id.production_id | ||
# work_center_cost = 0 | ||
# consumed_moves = production.move_raw_ids.filtered(lambda x: x.state == 'done') | ||
# finished_move = layer.stock_move_id | ||
# if finished_move and consumed_moves.mapped('lot_ids'): | ||
# finished_move.ensure_one() | ||
# # total_cost = finished_move.price_unit - (sum(-m.stock_valuation_layer_ids.value for m in consumed_moves.filtered(lambda sm : sm.product_id.tracking == 'serial'))) | ||
# qty_done = finished_move.product_uom._compute_quantity(finished_move.quantity_done, finished_move.product_id.uom_id) | ||
# raw_sn_price = sum(lot.real_price for lot in consumed_moves.mapped('lot_ids')) | ||
# raw_non_sn_price = - sum(consumed_moves.filtered(lambda l:l.product_id.tracking !='serial').sudo().stock_valuation_layer_ids.mapped('value')) | ||
# total_cost = finished_move.price_unit + raw_non_sn_price | ||
# byproduct_moves = production.move_byproduct_ids.filtered(lambda m: m.state in ('done', 'cancel') and m.quantity_done > 0) | ||
# byproduct_cost_share = 0 | ||
# for byproduct in byproduct_moves: | ||
# if byproduct.cost_share == 0: | ||
# continue | ||
# byproduct_cost_share += byproduct.cost_share | ||
# if byproduct.product_id.cost_method in ('fifo', 'average') and byproduct.product_id.tracking =='serial': | ||
# byproduct.price_unit = total_cost * byproduct.cost_share / 100 / byproduct.product_uom._compute_quantity(byproduct.quantity_done, byproduct.product_id.uom_id) | ||
# byproduct.stock_valuation_layer_ids.write({'unit_cost':byproduct.price_unit, | ||
# 'value':byproduct.price_unit* byproduct.product_uom._compute_quantity(byproduct.quantity_done, byproduct.product_id.uom_id), | ||
# 'remaining_value' : byproduct.price_unit* byproduct.product_uom._compute_quantity(byproduct.quantity_done, byproduct.product_id.uom_id) | ||
# }) | ||
# byproduct.lot_ids.write({'real_price':byproduct.price_unit}) | ||
# else: | ||
# byproduct.price_unit = total_cost * byproduct.cost_share / 100 / byproduct.product_uom._compute_quantity(byproduct.quantity_done, byproduct.product_id.uom_id) | ||
# byproduct.stock_valuation_layer_ids.write({'unit_cost':byproduct.price_unit, | ||
# 'value':byproduct.price_unit* byproduct.product_uom._compute_quantity(byproduct.quantity_done, byproduct.product_id.uom_id), | ||
# 'remaining_value':byproduct.price_unit* byproduct.product_uom._compute_quantity(byproduct.quantity_done, byproduct.product_id.uom_id)}) | ||
# if finished_move.product_id.cost_method in ('fifo', 'average'): | ||
# finished_move.price_unit = total_cost * float_round(1 - byproduct_cost_share / 100, precision_rounding=0.0001) / qty_done | ||
# production.lot_producing_id.real_price = finished_move.price_unit | ||
# layer.value = finished_move.price_unit | ||
# layer.unit_cost= finished_move.price_unit | ||
return res | ||
|
||
def svl_lots_update(self): | ||
# Update lot_ids on svl for existing records. | ||
for svl in self: | ||
svl.lot_ids = [ | ||
(6, 0, svl.stock_move_id.mapped("move_line_ids").mapped("lot_id").ids) | ||
] | ||
|
||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<odoo> | ||
<record id="inventory_valuation_lot_ids" model="ir.ui.view"> | ||
<field name="name">inventory.valuation.lot.ids</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="stock_move_id" position="after"> | ||
<field name="lot_ids" widget="many2many_tags" /> | ||
</field> | ||
</field> | ||
</record> | ||
</odoo> |