diff --git a/stock_location_history/README.rst b/stock_location_history/README.rst new file mode 100644 index 000000000000..683965b61a08 --- /dev/null +++ b/stock_location_history/README.rst @@ -0,0 +1,125 @@ +====================== +Stock Location History +====================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:8ef2e101fc89b5c5da4407bd4a206ff4616465f468c30b5d56604b0ba2299927 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-warehouse/tree/18.0/stock_location_history + :alt: OCA/stock-logistics-warehouse +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-18-0/stock-logistics-warehouse-18-0-stock_location_history + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-warehouse&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows you to manage stages of location and their history. +This was developed to manage silos and similar storage locations for +bulk products. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +- Go to Inventory > Configuration > Location Stages. + +- Click New to create a stage and configure only the following + parameters: + + - Name + - Sequence + - Status: Active + - Allowed Validation Groups + - Allowed Stage Change Groups + +- Set Stage Properties + +- For each stage, specify whether it applies to one of the following + categories (these fields are used for form validations): + + - Is First + - Is Final + - Is Pause + - Is Use + +- Configure Stage Transitions + +- After creating all the stages, define the allowed transitions for each + stage to establish a proper flow. + +Usage +===== + +- Go to Inventory > Operations > Locations +- Open a location to see its stages and history +- Use the buttons to change the stages of the location + +\* Based on its stage, the location will be available to receive a new +lot of bulk products or not. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Open Source Integrators + +Contributors +------------ + +- Johannan Luna + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-jasiel-OSI| image:: https://github.com/jasiel-OSI.png?size=40px + :target: https://github.com/jasiel-OSI + :alt: jasiel-OSI + +Current `maintainer `__: + +|maintainer-jasiel-OSI| + +This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_location_history/__init__.py b/stock_location_history/__init__.py new file mode 100644 index 000000000000..0650744f6bc6 --- /dev/null +++ b/stock_location_history/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_location_history/__manifest__.py b/stock_location_history/__manifest__.py new file mode 100644 index 000000000000..1ad4c11e6871 --- /dev/null +++ b/stock_location_history/__manifest__.py @@ -0,0 +1,19 @@ +{ + "name": "Stock Location History", + "version": "18.0.1.0.0", + "license": "AGPL-3", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "author": "Open Source Integrators, Odoo Community Association (OCA)", + "depends": ["stock", "gaqsa_plc"], + "category": "Stock", + "data": [ + # "data/stock_location_stage_data.xml", + "security/ir.model.access.csv", + "views/stock_location_views.xml", + "views/stock_location_stage_views.xml", + "views/stock_location_history_views.xml", + "views/stock_lot_views.xml", + ], + "development_status": "Beta", + "maintainers": ["jasiel-OSI"], +} diff --git a/stock_location_history/data/stock_location_stage_data.xml b/stock_location_history/data/stock_location_stage_data.xml new file mode 100644 index 000000000000..d2cc7b63c71f --- /dev/null +++ b/stock_location_history/data/stock_location_stage_data.xml @@ -0,0 +1,31 @@ + + + Empty + 10 + True + Location is empty. + + + + Ready + 20 + True + Location has been cleaned and is ready to + receive a new lot. + + + + Occupied + 30 + True + Location is occupied and contains a lot. + + + + Retired + 90 + True + True + Location has been retired. + + diff --git a/stock_location_history/models/__init__.py b/stock_location_history/models/__init__.py new file mode 100644 index 000000000000..9583ed368fd9 --- /dev/null +++ b/stock_location_history/models/__init__.py @@ -0,0 +1,4 @@ +from . import stock_location +from . import stock_location_stage +from . import stock_location_history +from . import stock_lot diff --git a/stock_location_history/models/stock_location.py b/stock_location_history/models/stock_location.py new file mode 100644 index 000000000000..208e75719b0f --- /dev/null +++ b/stock_location_history/models/stock_location.py @@ -0,0 +1,309 @@ +import logging + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + +_logger = logging.getLogger(__name__) + +_logger = logging.getLogger(__name__) + + +class StockLocation(models.Model): + _inherit = "stock.location" + + track_stage = fields.Boolean( + string="Track location stage?", + default=False, + help="If enabled, the location stages will be tracked.", + ) + + @api.onchange("track_stage") + def _onchange_track_stage(self): + if self.track_stage and not self.stage_id: + # Assign default stage when enabling tracking + default_stage = self.env["stock.location.stage"].search( + [("is_default", "=", True)], limit=1 + ) + if default_stage: + self.stage_id = default_stage + elif not self.track_stage and self.stage_id: + # Prevent disabling tracking if not on default stage + default_stage = self.env["stock.location.stage"].search( + [("is_default", "=", True)], limit=1 + ) + if default_stage and self.stage_id != default_stage: + raise ValidationError( + _("Cannot disable stage tracking while not on the default stage.") + ) + + @api.onchange("track_stage") + def _onchange_track_stage(self): + if self.track_stage and not self.stage_id: + # Assign default stage when enabling tracking + default_stage = self.env["stock.location.stage"].search( + [("is_default", "=", True)], limit=1 + ) + if default_stage: + self.stage_id = default_stage + elif not self.track_stage and self.stage_id: + # Prevent disabling tracking if not on default stage + default_stage = self.env["stock.location.stage"].search( + [("is_default", "=", True)], limit=1 + ) + if default_stage and self.stage_id != default_stage: + raise ValidationError( + _("Cannot disable stage tracking while not on the default stage.") + ) + + stage_id = fields.Many2one("stock.location.stage", string="Stage") + lot_id = fields.Many2one("stock.lot") + # product_id = fields.Many2one(related="lot_id.product_id", store=True) + actual_product_id = fields.Many2one( + "product.product", string="Product", domain=[("tracking", "=", "lot")] + ) + # product_id = fields.Many2one(related="lot_id.product_id", store=True) + actual_product_id = fields.Many2one( + "product.product", string="Product", domain=[("tracking", "=", "lot")] + ) + last_stage_id = fields.Many2one("stock.location.stage", string="Last stage") + last_stage_validated = fields.Boolean( + string="Is the last stage progress validated?" + ) + + def open_location_history(self): + self.ensure_one() + return { + "name": _("Location History"), + "type": "ir.actions.act_window", + "res_model": "stock.location.history", + "view_mode": "list,form", + "domain": [("location_id", "=", self.id)], + "context": {"default_location_id": self.id}, + } + + @api.constrains("stage_id") + def check_stage_change(self): + default_stage = self.env["stock.location.stage"].search( + [("is_default", "=", True)], limit=1 + ) + self.ensure_one() + # Check if we're trying to disable track_stage when not on default stage + # This runs when stage_id changes, so we check if track_stage is False + # but stage_id is not empty + if not self.track_stage and self.stage_id: + if default_stage and self.stage_id != default_stage: + raise ValidationError( + _("Cannot disable stage tracking while not on the default stage.") + ) + + if not self.last_stage_id and not self.stage_id.is_default: + if not self.actual_product_id: + raise ValidationError( + _("A product must be assigned before changing stages.") + ) + else: + new_lot = self.macrolotCreation() + self.lot_id = new_lot + + # Check if we're leaving the default stage; actual_product_id must be set + if self.last_stage_id and self.last_stage_id.is_default and self.stage_id: + # Check if we're moving away from default stage + if ( + default_stage + and self.last_stage_id == default_stage + and self.stage_id != default_stage + ): + # If there's no product assigned, prevent the change + if not self.actual_product_id: + raise ValidationError( + _("A product must be assigned before changing stages.") + ) + # If there is a product assigned, create a lot and assign it + else: + new_lot = self.macrolotCreation() + self.lot_id = new_lot + + # Check if we're transitioning FROM a closed stage TO a default stage + if ( + self.last_stage_id + and self.last_stage_id.is_closed + and self.stage_id + and self.stage_id.is_default + ): + # Clean up lot_id and actual_product_id fields + self.lot_id = False + self.actual_product_id = False + + if self.stage_id and not any( + g in self.env.user.groups_id for g in self.stage_id.change_group_ids + ): + raise ValidationError(_("You are not allowed to change the stage.")) + if ( + self.last_stage_id + and self.last_stage_id.validation + and not self.last_stage_validated + ): + raise ValidationError(_("Validation required")) + if self.stage_id != self.last_stage_id and self.last_stage_id: + if not self.validate_stage_route(): + raise ValidationError(_("Invalid stage change")) + else: + self.create_location_history(self.stage_id.name) + self.last_stage_id = self.stage_id + self.last_stage_validated = not self.stage_id.validation + + @api.constrains("location_history_ids") + def check_last_history_change(self): + last_history = self.env["stock.location.history"].search( + [("location_id", "=", self.id)], order="create_date desc", limit=1 + ) + if not last_history: + self.last_stage_validated = True + else: + if last_history.registry_type == "val": + self.last_stage_validated = True + + def validate_stage_route(self): + if not self.stage_id: + return True + destination_ids = self.last_stage_id.next_ids + return self.stage_id in destination_ids + + def create_location_history(self, registry_type): + history_vals = { + "location_id": self.id, + "lot_id": self.lot_id.id, + "previous_stage_id": self.last_stage_id.id, + "new_stage_id": self.stage_id.id, + "registry_type": registry_type, + "user_id": self.env.uid, + } + self.env["stock.location.history"].sudo().create(history_vals) + + def validate_stage(self): + if not any( + g in self.env.user.groups_id for g in self.stage_id.validation_group_ids + ): + raise ValidationError(_("You are not allowed to change the stage.")) + else: + self.create_location_history("Validation") + self.last_stage_validated = True + + def verifyBDSource(self): + """Fetch batch data from external PLC and create related records.""" + warehouse_model = self.env["stock.warehouse"] + try: + warehouses = warehouse_model.search([("dbsource_id", "!=", False)]) + except ValueError: + # Field dbsource_id doesn't exist (gaqsa_mrp not installed or not loaded) + _logger.warning( + "dbsource_id field not available in stock.warehouse. " + "Please ensure gaqsa_mrp module is installed." + ) + return + except Exception as e: + _logger.warning("Error searching warehouses with dbsource_id: %s", e) + return + if not warehouses: + _logger.info("No warehouses with dbsource_id configured.") + return + for warehouse in warehouses: + PLC_DB = warehouse.dbsource_id or False + return PLC_DB + + def PLC_Complete(self): + if self.getMacroFromPLC(): + self.updateMacroToPLC() + else: + self.pushMacroToPLC() + + def updateMacroToPLC(self): + PLC_DB = self.verifyBDSource() + + try: + with PLC_DB.connection_open() as conn: + silo = self.name + codmat = self.actual_product_id.default_code + macrolote = self.lot_id.name + + query = """UPDATE Macrolotes SET Macrolote =? + WHERE CodMat=? AND Silo=?""" + + conn.execute(query, (macrolote, codmat, silo)) + conn.commit() + + except Exception as e: + raise ValidationError(_(f"Error updating in MSSQL: {e}")) from e + + def pushMacroToPLC(self): + PLC_DB = self.verifyBDSource() + + try: + with PLC_DB.connection_open() as conn: + silo = self.name + codmat = self.actual_product_id.default_code + nommat = self.actual_product_id.name + macrolote = self.lot_id.name + + query = """INSERT INTO Macrolotes (Silo, CodMat, NomMat, Macrolote) + VALUES(?,?,?,?);""" + conn.execute(query, (silo, codmat, nommat, macrolote)) + conn.commit() + + except Exception as e: + raise ValidationError(_(f"Error inserting in MSSQL: {e}")) from e + + def getMacroFromPLC(self): + PLC_DB = self.verifyBDSource() + codmat = self.actual_product_id.default_code + silo = self.name + try: + with PLC_DB.connection_open() as conn: + query = ( + "SELECT id, Silo, CodMat, NomMat, Macrolote " + "FROM Macrolotes WHERE CodMat=? AND Silo=?;" + ) + + result = conn.execute(query, (codmat, silo)) + + rows = result.fetchall() + + if not rows: + return False + else: + return True + except Exception as e: + raise ValidationError(_(f"Error querying in MSSQL: {e}")) from e + + def macrolotCreation(self): + # Create a new lot with the requested naming convention + # Format: -<5-digit_consecutive_number> + location_name = self.name.replace(" ", "_").upper()[ + :10 + ] # Take first 10 chars, uppercase, remove spaces + # Count existing lots for this location + existing_lots = self.env["stock.lot"].search( + [("name", "like", f"{location_name}-")] + ) + + max_number = 0 + for lot in existing_lots: + if "-" in lot.name: + suffix = lot.name.split("-")[-1] + if suffix.isdigit(): + num = int(suffix) + if num > max_number: + max_number = num + + next_number = max_number + 1 + + lot_name = f"{location_name}-{next_number:05d}" + lot_vals = { + "name": lot_name, + "product_id": self.actual_product_id.id, + "company_id": self.env.company.id, + "location_id": self.id, + } + new_lot = self.env["stock.lot"].sudo().create(lot_vals) + self.with_delay().PLC_Complete() + return new_lot diff --git a/stock_location_history/models/stock_location_history.py b/stock_location_history/models/stock_location_history.py new file mode 100644 index 000000000000..65150fa204ce --- /dev/null +++ b/stock_location_history/models/stock_location_history.py @@ -0,0 +1,16 @@ +from odoo import fields, models + + +class StockLocationHistory(models.Model): + _name = "stock.location.history" + _description = "Stock Location History" + _order = "date desc" + + location_id = fields.Many2one("stock.location", string="Location", required=True) + product_id = fields.Many2one(related="lot_id.product_id", store=True) + lot_id = fields.Many2one("stock.lot", string="Lot/Serial Number") + previous_stage_id = fields.Many2one("stock.location.stage", string="Previous Stage") + new_stage_id = fields.Many2one("stock.location.stage", string="New Stage") + user_id = fields.Many2one("res.users", string="Performed by") + date = fields.Datetime(default=fields.Datetime.now) + registry_type = fields.Char() diff --git a/stock_location_history/models/stock_location_stage.py b/stock_location_history/models/stock_location_stage.py new file mode 100644 index 000000000000..d0e289d56d63 --- /dev/null +++ b/stock_location_history/models/stock_location_stage.py @@ -0,0 +1,50 @@ +from odoo import fields, models + + +class StockLocationStage(models.Model): + _name = "stock.location.stage" + _description = "Stock Location Stage" + _order = "sequence, name, id" + + name = fields.Char(required=True) + description = fields.Text() + validation = fields.Boolean(string="Requires validation?", default=False) + sequence = fields.Integer(default=1) + active = fields.Boolean(help="If active, the stage will be displayed", default=True) + fold = fields.Boolean( + "Folded in Kanban", + help="This stage is folded in the kanban view when " + "there are no record in that stage to display.", + ) + is_closed = fields.Boolean( + "Is a close stage", help="Locations in this stage are considered as closed." + ) + is_default = fields.Boolean("Is a default stage", help="Used a default stage") + next_ids = fields.Many2many( + "stock.location.stage", + "location_stage_rel", + "stage_id", + "allowed_stage_id", + string="Allowed next stages", + ) + is_use = fields.Boolean( + string="Usage stage?", + help="Mark this stage as a usage stage if raw material is being taken " + "from the location", + ) + validation_group_ids = fields.Many2many( + "res.groups", + "location_stage_validation_group_rel", + "stage_id", + "group_id", + string="Groups allowed to validate", + help="Only users in this group can validate the current stage", + ) + change_group_ids = fields.Many2many( + "res.groups", + "location_stage_change_group_rel", + "stage_id", + "group_id", + string="Groups allowed to change the stage", + help="Only users in this group can move the process forward from this stage", + ) diff --git a/stock_location_history/models/stock_lot.py b/stock_location_history/models/stock_lot.py new file mode 100644 index 000000000000..5517781a6bc1 --- /dev/null +++ b/stock_location_history/models/stock_lot.py @@ -0,0 +1,132 @@ +from odoo import api, fields, models + + +class StockLotMacrolot(models.Model): + _inherit = "stock.lot" + + quantity_in = fields.Float(help="Everything that has entered") + first_ticket = fields.Char( + compute="_compute_ticket_data", store=True, help="First ticket name" + ) + + date_first_ticket = fields.Date( + compute="_compute_ticket_data", store=True, help="First ticket date" + ) + + last_ticket = fields.Char( + compute="_compute_ticket_data", store=True, help="Last ticket name" + ) + + date_last_ticket = fields.Date( + compute="_compute_ticket_data", store=True, help="Last ticket date" + ) + + quantity_difference = fields.Float(help="Quantity on hand") + total_consumption = fields.Float(help="Total consumed") + + initial_mix = fields.Char( + compute="_compute_mix_data", store=True, help="Initial mix code" + ) + + initial_mix_date = fields.Date( + compute="_compute_mix_data", store=True, help="First mix date" + ) + + final_mix = fields.Char(compute="_compute_mix_data", store=True, help="Final Mix") + + final_mix_date = fields.Date( + compute="_compute_mix_data", store=True, help="Last mix date" + ) + + kg_consumed_mi = fields.Float( + compute="_compute_mix_data", + store=True, + help="Kilograms consumed from the Initial Mix", + ) + + kg_consumed_mf = fields.Float( + compute="_compute_mix_data", + store=True, + help="Kilograms consumed from the Final Mix", + ) + + batch_line_ids = fields.One2many( + comodel_name="mrp.batch.lines", inverse_name="lot_id", string="Batch Lines" + ) + + @api.depends("product_qty") + def _compute_ticket_data(self): + for lot in self: + # In Odoo 18, we query stock.move.line directly to find moves for this lot + move_lines = self.env["stock.move.line"].search( + [ + ("lot_id", "=", lot.id), + ("move_id.picking_id.picking_type_id.code", "=", "incoming"), + ("move_id.picking_id.state", "=", "done"), + ] + ) + move_lines = move_lines.sorted( + key=lambda ml: (ml.move_id.picking_id.date_done or ml.move_id.date) + ) + + if not move_lines: + lot.first_ticket = False + lot.last_ticket = False + lot.date_first_ticket = False + lot.date_last_ticket = False + continue + + # Get first and last tickets + first_line = move_lines[0] + last_line = move_lines[-1] + + lot.first_ticket = first_line.move_id.picking_id.name + lot.last_ticket = last_line.move_id.picking_id.name + lot.date_first_ticket = ( + first_line.move_id.picking_id.date_done or first_line.move_id.date + ) + lot.date_last_ticket = ( + last_line.move_id.picking_id.date_done or last_line.move_id.date + ) + + @api.depends("batch_line_ids.batch_id") + def _compute_mix_data(self): + for lot in self: + lines = lot.batch_line_ids + if not lines: + lot.initial_mix = False + lot.final_mix = False + lot.initial_mix_date = False + lot.final_mix_date = False + lot.kg_consumed_mi = 0.0 + lot.kg_consumed_mf = 0.0 + continue + + batches = lines.mapped("batch_id") + batches = batches.sorted(key=lambda b: b.date_start or b.create_date) + + first = batches[0] + last = batches[-1] + + lot.initial_mix = first.num_mez + lot.final_mix = last.num_mez + lot.initial_mix_date = first.date_start or first.create_date + lot.final_mix_date = last.date_start or last.create_date + + # Calcular kg_consumed_mi (kilogramos consumidos de la mezcla inicial) + first_num_mez = first.num_mez + initial_mix_lines = lines.filtered( + lambda line, first_num_mez=first_num_mez: ( + line.batch_id.num_mez == first_num_mez + ) + ) + lot.kg_consumed_mi = sum(initial_mix_lines.mapped("weight")) + + # Calcular kg_consumed_mf (kilogramos consumidos de la mezcla final) + last_num_mez = last.num_mez + final_mix_lines = lines.filtered( + lambda line, last_num_mez=last_num_mez: ( + line.batch_id.num_mez == last_num_mez + ) + ) + lot.kg_consumed_mf = sum(final_mix_lines.mapped("weight")) diff --git a/stock_location_history/pyproject.toml b/stock_location_history/pyproject.toml new file mode 100644 index 000000000000..4231d0cccb3d --- /dev/null +++ b/stock_location_history/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/stock_location_history/readme/CONFIGURE.md b/stock_location_history/readme/CONFIGURE.md new file mode 100644 index 000000000000..16356dcf9629 --- /dev/null +++ b/stock_location_history/readme/CONFIGURE.md @@ -0,0 +1,25 @@ +- Go to Inventory \> Configuration \> Location Stages. + +- Click New to create a stage and configure only the following + parameters: + + > - Name + > - Sequence + > - Status: Active + > - Allowed Validation Groups + > - Allowed Stage Change Groups + +- Set Stage Properties + +- For each stage, specify whether it applies to one of the following + categories (these fields are used for form validations): + + > - Is First + > - Is Final + > - Is Pause + > - Is Use + +- Configure Stage Transitions + +- After creating all the stages, define the allowed transitions for each + stage to establish a proper flow. diff --git a/stock_location_history/readme/CONTRIBUTORS.md b/stock_location_history/readme/CONTRIBUTORS.md new file mode 100644 index 000000000000..e70046640858 --- /dev/null +++ b/stock_location_history/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Johannan Luna \<\> diff --git a/stock_location_history/readme/DESCRIPTION.md b/stock_location_history/readme/DESCRIPTION.md new file mode 100644 index 000000000000..97857c59e641 --- /dev/null +++ b/stock_location_history/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module allows you to manage stages of location and their history. +This was developed to manage silos and similar storage locations for +bulk products. diff --git a/stock_location_history/readme/USAGE.md b/stock_location_history/readme/USAGE.md new file mode 100644 index 000000000000..1dbbcec91e55 --- /dev/null +++ b/stock_location_history/readme/USAGE.md @@ -0,0 +1,6 @@ +- Go to Inventory \> Operations \> Locations +- Open a location to see its stages and history +- Use the buttons to change the stages of the location + +\* Based on its stage, the location will be available to receive a new +lot of bulk products or not. diff --git a/stock_location_history/security/ir.model.access.csv b/stock_location_history/security/ir.model.access.csv new file mode 100644 index 000000000000..32faba6cf6b5 --- /dev/null +++ b/stock_location_history/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_stock_location_stage_stock_user,access_stock_location_stage_user,model_stock_location_stage,stock.group_stock_user,1,0,0,0 +access_stock_location_stage_stock_manager,access_stock_location_stage_manager,model_stock_location_stage,stock.group_stock_manager,1,1,1,1 +access_stock_location_history_stock_user,access_stock_location_history_user,model_stock_location_history,stock.group_stock_user,1,0,0,0 diff --git a/stock_location_history/static/description/icon.png b/stock_location_history/static/description/icon.png new file mode 100644 index 000000000000..3a0328b516c4 Binary files /dev/null and b/stock_location_history/static/description/icon.png differ diff --git a/stock_location_history/static/description/index.html b/stock_location_history/static/description/index.html new file mode 100644 index 000000000000..71837eb287d1 --- /dev/null +++ b/stock_location_history/static/description/index.html @@ -0,0 +1,476 @@ + + + + + +Stock Location History + + + +
+

Stock Location History

+ + +

Beta License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runboat

+

This module allows you to manage stages of location and their history. +This was developed to manage silos and similar storage locations for +bulk products.

+

Table of contents

+ +
+

Configuration

+
    +
  • Go to Inventory > Configuration > Location Stages.

    +
  • +
  • Click New to create a stage and configure only the following +parameters:

    +
    +
      +
    • Name
    • +
    • Sequence
    • +
    • Status: Active
    • +
    • Allowed Validation Groups
    • +
    • Allowed Stage Change Groups
    • +
    +
    +
  • +
  • Set Stage Properties

    +
  • +
  • For each stage, specify whether it applies to one of the following +categories (these fields are used for form validations):

    +
    +
      +
    • Is First
    • +
    • Is Final
    • +
    • Is Pause
    • +
    • Is Use
    • +
    +
    +
  • +
  • Configure Stage Transitions

    +
  • +
  • After creating all the stages, define the allowed transitions for each +stage to establish a proper flow.

    +
  • +
+
+
+

Usage

+
    +
  • Go to Inventory > Operations > Locations
  • +
  • Open a location to see its stages and history
  • +
  • Use the buttons to change the stages of the location
  • +
+

* Based on its stage, the location will be available to receive a new +lot of bulk products or not.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Open Source Integrators
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

jasiel-OSI

+

This module is part of the OCA/stock-logistics-warehouse project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/stock_location_history/views/stock_location_history_views.xml b/stock_location_history/views/stock_location_history_views.xml new file mode 100644 index 000000000000..76a261fab848 --- /dev/null +++ b/stock_location_history/views/stock_location_history_views.xml @@ -0,0 +1,33 @@ + + + + stock.location.history.list + stock.location.history + + + + + + + + + + + + + + + + Locations + stock.location.history + list + + + + diff --git a/stock_location_history/views/stock_location_stage_views.xml b/stock_location_history/views/stock_location_stage_views.xml new file mode 100644 index 000000000000..4870e21db68f --- /dev/null +++ b/stock_location_history/views/stock_location_stage_views.xml @@ -0,0 +1,70 @@ + + + stock.location.stage.list + stock.location.stage + + + + + + + + + + + + stock.location.stage.form + stock.location.stage + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+ + + Location Stages + stock.location.stage + list,form + +

+ No stages defined yet. +

+

You can create a new stage from here.

+
+
+ + + +
diff --git a/stock_location_history/views/stock_location_views.xml b/stock_location_history/views/stock_location_views.xml new file mode 100644 index 000000000000..6335e7434702 --- /dev/null +++ b/stock_location_history/views/stock_location_views.xml @@ -0,0 +1,77 @@ + + + + stock.location.form + stock.location + + + +