Skip to content

Commit

Permalink
[ADD] Added osi_fifo_serialized_fix for Cab
Browse files Browse the repository at this point in the history
  • Loading branch information
Chanakya-OSI committed Nov 24, 2023
1 parent ba4fadf commit bf5fbe2
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 0 deletions.
30 changes: 30 additions & 0 deletions osi_fifo_serialized_fix/README.rst
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]>
1 change: 1 addition & 0 deletions osi_fifo_serialized_fix/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
18 changes: 18 additions & 0 deletions osi_fifo_serialized_fix/__manifest__.py
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,
}
5 changes: 5 additions & 0 deletions osi_fifo_serialized_fix/models/__init__.py
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
159 changes: 159 additions & 0 deletions osi_fifo_serialized_fix/models/stock_move.py
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 osi_fifo_serialized_fix/models/stock_valuation_layer.py
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)
]






12 changes: 12 additions & 0 deletions osi_fifo_serialized_fix/views/stock_valuation_layer.xml
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>

0 comments on commit bf5fbe2

Please sign in to comment.