diff --git a/altinkaya_stock_lot/__init__.py b/altinkaya_stock_lot/__init__.py new file mode 100644 index 000000000..6fa69c9cb --- /dev/null +++ b/altinkaya_stock_lot/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2022 Yiğit Budak (https://github.com/yibudak) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from . import models +# from . import wizards diff --git a/altinkaya_stock_lot/__manifest__.py b/altinkaya_stock_lot/__manifest__.py index b2bfceb2e..586f9f0bd 100644 --- a/altinkaya_stock_lot/__manifest__.py +++ b/altinkaya_stock_lot/__manifest__.py @@ -1,18 +1,18 @@ -# Copyright 2022 Yiğit Budak (https://github.com/yibudak) +# Copyright 2025 Yiğit Budak, Ümithan Güldemir (https://github.com/yibudak) (https://github.com/umithan-guldemir) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { "name": "Altinkaya Stock Lot Extensions", "summary": "Adds custom fields to stock.production.lot", - "version": "13.0.1.0.0", + "version": "16.0.1.0.0", "category": "stock", "website": "https://github.com/yibudak", - "author": "Yiğit Budak", + "author": "Yiğit Budak, Ümithan Güldemir", "license": "AGPL-3", "application": False, "installable": True, - "depends": ["stock", "mrp"], + "depends": ["stock", "mrp", "stock_inventory"], "data": [ - "wizards/mrp_product_produce_view.xml", + # "wizards/mrp_product_produce_view.xml", ], } diff --git a/altinkaya_stock_lot/i18n/altinkaya_stock_lot.pot b/altinkaya_stock_lot/i18n/altinkaya_stock_lot.pot new file mode 100644 index 000000000..cc93dbc7d --- /dev/null +++ b/altinkaya_stock_lot/i18n/altinkaya_stock_lot.pot @@ -0,0 +1,48 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * altinkaya_stock_lot +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-12-28 08:07+0000\n" +"PO-Revision-Date: 2022-12-28 08:07+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: altinkaya_stock_lot +#: model:ir.model,name:altinkaya_stock_lot.model_stock_production_lot +msgid "Lot/Serial" +msgstr "" + +#. module: altinkaya_stock_lot +#: model:ir.model.fields,field_description:altinkaya_stock_lot.field_stock_production_lot__name +msgid "Lot/Serial Number" +msgstr "" + +#. module: altinkaya_stock_lot +#: model:ir.model,name:altinkaya_stock_lot.model_mrp_product_produce +msgid "Record Production" +msgstr "" + +#. module: altinkaya_stock_lot +#: model:ir.model,name:altinkaya_stock_lot.model_stock_picking +msgid "Transfer" +msgstr "" + +#. module: altinkaya_stock_lot +#: model:ir.model.fields,help:altinkaya_stock_lot.field_stock_production_lot__name +msgid "Unique Lot/Serial Number" +msgstr "" + +#. module: altinkaya_stock_lot +#: code:addons/altinkaya_stock_lot/wizards/mrp_product_produce.py:16 +#, python-format +msgid "You have to select a product." +msgstr "" + diff --git a/altinkaya_stock_lot/i18n/tr.po b/altinkaya_stock_lot/i18n/tr.po new file mode 100644 index 000000000..a6dabd830 --- /dev/null +++ b/altinkaya_stock_lot/i18n/tr.po @@ -0,0 +1,48 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * altinkaya_stock_lot +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-12-28 08:07+0000\n" +"PO-Revision-Date: 2022-12-28 08:07+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: altinkaya_stock_lot +#: model:ir.model,name:altinkaya_stock_lot.model_stock_production_lot +msgid "Lot/Serial" +msgstr "Lot/Seri" + +#. module: altinkaya_stock_lot +#: model:ir.model.fields,field_description:altinkaya_stock_lot.field_stock_production_lot__name +msgid "Lot/Serial Number" +msgstr "Lot/Seri Numarası" + +#. module: altinkaya_stock_lot +#: model:ir.model,name:altinkaya_stock_lot.model_mrp_product_produce +msgid "Record Production" +msgstr "Üretimi Kaydını İşle" + +#. module: altinkaya_stock_lot +#: model:ir.model,name:altinkaya_stock_lot.model_stock_picking +msgid "Transfer" +msgstr "Transfer" + +#. module: altinkaya_stock_lot +#: model:ir.model.fields,help:altinkaya_stock_lot.field_stock_production_lot__name +msgid "Unique Lot/Serial Number" +msgstr "Benzersiz Lot / Seri Numarası" + +#. module: altinkaya_stock_lot +#: code:addons/altinkaya_stock_lot/wizards/mrp_product_produce.py:16 +#, python-format +msgid "You have to select a product." +msgstr "Üretilecek bir ürün seçmelisiniz." + diff --git a/altinkaya_stock_lot/models/__init__.py b/altinkaya_stock_lot/models/__init__.py new file mode 100644 index 000000000..aeb5521cf --- /dev/null +++ b/altinkaya_stock_lot/models/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2022 Yiğit Budak (https://github.com/yibudak) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from . import stock_lot +from . import stock_move_line +# from . import stock_quant diff --git a/altinkaya_stock_lot/models/stock_inventory.py b/altinkaya_stock_lot/models/stock_inventory.py deleted file mode 100644 index 384a869ce..000000000 --- a/altinkaya_stock_lot/models/stock_inventory.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright 2022 Yiğit Budak (https://github.com/yibudak) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import models, api - - -class StockInventory(models.Model): - _inherit = "stock.inventory" - - - def action_validate(self): - return super(StockInventory, self).action_validate() diff --git a/altinkaya_stock_lot/models/stock_inventory_line.py b/altinkaya_stock_lot/models/stock_inventory_line.py deleted file mode 100644 index 1856f7286..000000000 --- a/altinkaya_stock_lot/models/stock_inventory_line.py +++ /dev/null @@ -1,44 +0,0 @@ -from odoo import api, models - - -class InventoryLine(models.Model): - _inherit = "stock.inventory.line" - - - def _create_missing_lot(self): - """EXPERIMENTAL: Create a lot for the move line if it is missing.""" - for rec in self: - if rec.product_id.tracking != "none" and not rec.prod_lot_id: - # yigit: When working with negative quantities, any lot without quant - # is causing issues, try to search quant and link it to lot - related_quant = rec.env["stock.quant"].search( - [ - ("product_id", "=", rec.product_id.id), - ("location_id", "=", rec.inventory_location_id.id), - ("quantity", "=", rec.product_qty), - ("lot_id", "=", False), - ], - limit=1, - ) - prod_lot_id = self.env["stock.production.lot"].create( - { - "product_id": rec.product_id.id, - "ref": rec.inventory_id.name or "", - "product_qty": rec.product_qty, - "quant_ids": [(6, 0, related_quant.ids)], - } - ) - rec.prod_lot_id = prod_lot_id.id - return True - - @api.model - def create(self, vals): - res = super(InventoryLine, self).create(vals) - res._create_missing_lot() - return res - - @api.model - def write(self, vals): - res = super(InventoryLine, self).write(vals) - self._create_missing_lot() - return res diff --git a/altinkaya_stock_lot/models/stock_lot.py b/altinkaya_stock_lot/models/stock_lot.py new file mode 100644 index 000000000..9c0e83e74 --- /dev/null +++ b/altinkaya_stock_lot/models/stock_lot.py @@ -0,0 +1,28 @@ +# Copyright 2022 Yiğit Budak (https://github.com/yibudak) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +import random + +from odoo import fields, models + + +class StockProductionLot(models.Model): + _inherit = "stock.lot" + + def _compute_lot_name(self): + """Generates a random lot number. Overrides the default method. + We excluded some characters from the random string to avoid confusion.""" + while True: + unique = "".join( + random.choice("ABCDEFGHJKLMNPRSTUVXYZ123456789") for i in range(5) + ) + if not self.search([("name", "=", unique)], limit=1): + break + return unique + + name = fields.Char( + "Lot/Serial Number", + default=_compute_lot_name, + required=True, + readonly=True, + help="Unique Lot/Serial Number", + ) diff --git a/altinkaya_stock_lot/models/stock_move_line.py b/altinkaya_stock_lot/models/stock_move_line.py index 4093da447..8fb87de49 100644 --- a/altinkaya_stock_lot/models/stock_move_line.py +++ b/altinkaya_stock_lot/models/stock_move_line.py @@ -1,30 +1,29 @@ # Copyright 2022 Yiğit Budak (https://github.com/yibudak) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import models, api +from odoo import api, models class StockMoveLine(models.Model): _inherit = "stock.move.line" - def _create_missing_lot(self): - """EXPERIMENTAL: Create a lot for the move line if it is missing.""" + """Create a lot for the move line if it is missing.""" for rec in self: if rec.product_id.tracking != "none" and not rec.lot_id: - lot_id = self.env["stock.production.lot"].create( - {"product_id": rec.product_id.id, "ref": rec.move_id.name or ""} + lot_id = self.env["stock.lot"].create( + {"product_id": rec.product_id.id, "ref": rec.move_id.reference or ""} ) rec.lot_id = lot_id.id return True @api.model - def create(self, vals): - res = super(StockMoveLine, self).create(vals) + def create(self, vals_list): + res = super().create(vals_list) res._create_missing_lot() return res @api.model def write(self, vals): - res = super(StockMoveLine, self).write(vals) + res = super().write(vals) self._create_missing_lot() return res diff --git a/altinkaya_stock_lot/models/stock_picking.py b/altinkaya_stock_lot/models/stock_picking.py deleted file mode 100644 index 5c7298002..000000000 --- a/altinkaya_stock_lot/models/stock_picking.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2022 Yiğit Budak (https://github.com/yibudak) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import models, api -from odoo.tools import float_compare, float_is_zero - - -class StockPickin(models.Model): - _inherit = "stock.picking" - - # - # def button_validate(self): - # """Overridden to create lot_id for move_lines automatically""" - # picking_type = self.picking_type_id - # precision_digits = self.env["decimal.precision"].precision_get( - # "Product Unit of Measure" - # ) - # no_quantities_done = all( - # float_is_zero(move_line.qty_done, precision_digits=precision_digits) - # for move_line in self.move_line_ids.filtered( - # lambda m: m.state not in ("done", "cancel") - # ) - # ) - # if picking_type.use_create_lots or picking_type.use_existing_lots: - # lines_to_check = self.move_line_ids - # if not no_quantities_done: - # lines_to_check = lines_to_check.filtered( - # lambda ml: float_compare( - # ml.qty_done, - # 0, - # precision_rounding=ml.product_uom_id.rounding, - # ) - # ) - # for line in lines_to_check: - # product = line.product_id - # if product and product.tracking != "none": - # if not line.lot_name and not line.lot_id: - # vals = { - # "product_id": product.id, - # "ref": self.origin or "", - # } - # created_lot = line.lot_id.create(vals) - # line.write( - # {"lot_id": created_lot.id, "lot_name": created_lot.name} - # ) - # return super(StockPickin, self).button_validate() diff --git a/altinkaya_stock_lot/models/stock_quant.py b/altinkaya_stock_lot/models/stock_quant.py new file mode 100644 index 000000000..5e7a37b64 --- /dev/null +++ b/altinkaya_stock_lot/models/stock_quant.py @@ -0,0 +1,28 @@ +from odoo import api, models + + +class StockQuant(models.Model): + _inherit = "stock.quant" + + def _create_missing_lot(self, vals_list): + """EXPERIMENTAL: Create a lot for the move line if it is missing.""" + for vals in vals_list: + if not vals.get("lot_id") and vals.get("product_id") and (vals.get("quantity") or vals.get("inventory_quantity")): + product_id = self.env["product.product"].browse(vals["product_id"]) + if product_id.tracking != "none": + lot_id = self.env["stock.lot"].create( + { + "product_id": product_id.id, + "ref": vals.get("stock_inventory_ids") or "", #todo: find a better ref + "product_qty": vals.get("quantity") or vals.get("inventory_quantity"), + } + ) + vals["lot_id"] = lot_id.id + + + @api.model_create_multi + def create(self, vals_list): + self._create_missing_lot(vals_list) + res = super().create(vals_list) + + return res diff --git a/altinkaya_stock_lot/static/description/icon.png b/altinkaya_stock_lot/static/description/icon.png new file mode 100644 index 000000000..e21c89070 Binary files /dev/null and b/altinkaya_stock_lot/static/description/icon.png differ diff --git a/altinkaya_stock_lot/wizards/__init__.py b/altinkaya_stock_lot/wizards/__init__.py new file mode 100644 index 000000000..2f8570c2e --- /dev/null +++ b/altinkaya_stock_lot/wizards/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2022 Yiğit Budak (https://github.com/yibudak) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +from . import mrp_product_produce \ No newline at end of file diff --git a/altinkaya_stock_lot/wizards/mrp_product_produce.py b/altinkaya_stock_lot/wizards/mrp_product_produce.py index febc70b51..73f14fd7a 100644 --- a/altinkaya_stock_lot/wizards/mrp_product_produce.py +++ b/altinkaya_stock_lot/wizards/mrp_product_produce.py @@ -1,14 +1,13 @@ -# Copyright 2022 Yiğit Budak (https://github.com/yibudak) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import models, fields, api, _ -from odoo.exceptions import UserError -from odoo.tools import float_is_zero +# # Copyright 2025 Yiğit Budak, Ümithan Güldemir (https://github.com/yibudak) (https://github.com/umithan-guldemir) +# # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +# from odoo import models, fields, api, _ +# from odoo.exceptions import UserError +# from odoo.tools import float_is_zero -class MrpProductProduce(models.TransientModel): - _inherit = "mrp.product.produce" +# class MrpProductProduce(models.TransientModel): +# _inherit = "mrp.production" - def do_produce(self): """Override do_produce method on MRP to generate lot_id automatically""" if any( @@ -18,47 +17,54 @@ def do_produce(self): and not float_is_zero( x.qty_done, precision_rounding=x.product_uom_id.rounding ) - for x in self.produce_line_ids + for x in self.finished_move_line_ids ] ): raise UserError(_("Some products are tracked by lots but no lot is set.")) if self.product_tracking != "none" and not self.lot_id: # If lot created within label printing wizard, use it - if self.production_id.lot_id_to_create: - self.lot_id = self.production_id.lot_id_to_create + if self.lot_producing_id: + self.lot_id = self.lot_producing_id else: vals = { "product_id": self.product_id.id, - "ref": self.production_id.origin or "", + "ref": self.origin or "", } self.lot_id = self.lot_id.create(vals) - return super(MrpProductProduce, self).do_produce() + return self.do_produce() - @api.onchange("product_qty") - def _onchange_product_qty(self): - """Override _onchange_product_qty method on MRP to remove duplicate - rows caused by split procurement rules""" - res = super(MrpProductProduce, self)._onchange_product_qty() - for line in self.produce_line_ids.filtered(lambda p: not p.lot_id): - real_line = self.produce_line_ids.filtered( - lambda x: x.qty_to_consume == line.qty_to_consume - and x.product_id == line.product_id - and x.lot_id - ) - if real_line: - self.produce_line_ids -= line - return res +# @api.onchange("product_qty") +# def _onchange_product_qty(self): +# """Override _onchange_product_qty method on MRP to remove duplicate +# rows caused by split procurement rules""" +# res = self._onchange_product_qty() +# for line in self.finished_move_line_ids.filtered(lambda p: not p.lot_id): +# real_line = self.finished_move_line_ids.filtered( +# lambda x: x.reserved_qty == line.reserved_qty +# and x.product_id == line.product_id +# and x.lot_id +# ) +# if real_line: +# self.finished_move_line_ids -= line +# return res -class MrpProductProduceLine(models.TransientModel): - _inherit = "mrp.product.produce.line" +# class MrpProductProduceLine(models.TransientModel): +# _inherit = "mrp.production.backorder.line" - # We've added this field to prevent selection of a lot that is not in the - # raw materials location of the production order. - location_src_id = fields.Many2one( - "stock.location", - "Raw Materials Location", - related="raw_product_produce_id.production_id.location_src_id", - readonly=True, - ) +# # We've added this field to prevent selection of a lot that is not in the +# # raw materials location of the production order. +# production_id = fields.Many2one( +# "mrp.production", +# "Production Order", +# required=True, +# ondelete="cascade", +# index=True, +# ) +# location_src_id = fields.Many2one( +# "stock.location", +# "Raw Materials Location", +# related="production_id.location_src_id", +# readonly=True, +# ) diff --git a/altinkaya_stock_lot/wizards/mrp_product_produce_view.xml b/altinkaya_stock_lot/wizards/mrp_product_produce_view.xml index 6a8248860..5d414bc73 100644 --- a/altinkaya_stock_lot/wizards/mrp_product_produce_view.xml +++ b/altinkaya_stock_lot/wizards/mrp_product_produce_view.xml @@ -1,4 +1,4 @@ - +