mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[ADD] repair_picking
This commit is contained in:
committed by
Lois Rilo
parent
1ee992a8e4
commit
cf662b87cb
0
repair_picking/README.rst
Normal file
0
repair_picking/README.rst
Normal file
2
repair_picking/__init__.py
Normal file
2
repair_picking/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import models
|
||||
from . import tests
|
||||
20
repair_picking/__manifest__.py
Normal file
20
repair_picking/__manifest__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
{
|
||||
"name": "Repair Picking",
|
||||
"version": "14.0.1.0.0",
|
||||
"author": "ForgeFlow, Odoo Community Association (OCA)",
|
||||
"category": "Repair",
|
||||
"website": "https://github.com/OCA/manufacture",
|
||||
"summary": "Enhanced repair order management with pickings "
|
||||
"for adding and removing components",
|
||||
"depends": ["repair", "stock_move_forced_lot", "repair_stock_move"],
|
||||
"data": [
|
||||
"views/stock_warehouse_views.xml",
|
||||
"views/repair_order_view.xml",
|
||||
],
|
||||
"license": "LGPL-3",
|
||||
"installable": True,
|
||||
"auto_install": False,
|
||||
}
|
||||
3
repair_picking/models/__init__.py
Normal file
3
repair_picking/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import stock_warehouse
|
||||
from . import repair
|
||||
from . import stock_rule
|
||||
231
repair_picking/models/repair.py
Normal file
231
repair_picking/models/repair.py
Normal file
@@ -0,0 +1,231 @@
|
||||
# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class RepairOrder(models.Model):
|
||||
_inherit = "repair.order"
|
||||
|
||||
@api.model
|
||||
def _get_default_location_id(self):
|
||||
warehouse = self.env["stock.warehouse"].search(
|
||||
[("company_id", "=", self.env.company.id)], limit=1
|
||||
)
|
||||
return (
|
||||
warehouse.repair_location_id.id
|
||||
if warehouse and warehouse.repair_location_id
|
||||
else False
|
||||
)
|
||||
|
||||
# Changing default value on existing field
|
||||
location_id = fields.Many2one(
|
||||
default=_get_default_location_id,
|
||||
)
|
||||
|
||||
picking_ids = fields.Many2many(
|
||||
comodel_name="stock.picking",
|
||||
compute="_compute_picking_ids",
|
||||
copy=False,
|
||||
string="Pickings associated to this repair order",
|
||||
)
|
||||
picking_count = fields.Integer(
|
||||
string="Transfers", copy=False, compute="_compute_picking_ids"
|
||||
)
|
||||
procurement_group_id = fields.Many2one(
|
||||
"procurement.group", "Procurement Group", copy=False
|
||||
)
|
||||
|
||||
def action_view_pickings(self):
|
||||
self.ensure_one()
|
||||
action = self.env["ir.actions.actions"]._for_xml_id(
|
||||
"stock.action_picking_tree_all"
|
||||
)
|
||||
action["domain"] = [("id", "in", self.picking_ids.ids)]
|
||||
return action
|
||||
|
||||
def _compute_picking_ids(self):
|
||||
for order in self:
|
||||
moves = self.env["stock.move"].search(
|
||||
[("repair_line_id", "in", order.operations.ids)]
|
||||
)
|
||||
order.picking_ids = moves.mapped("picking_id")
|
||||
order.picking_count = len(moves.mapped("picking_id"))
|
||||
|
||||
def action_repair_cancel(self):
|
||||
res = super().action_repair_cancel()
|
||||
for picking in self.picking_ids:
|
||||
if picking.state not in ["cancel", "done"]:
|
||||
picking.action_cancel()
|
||||
return res
|
||||
|
||||
def _action_launch_stock_rule(self, repair_lines):
|
||||
for line in repair_lines:
|
||||
self._run_procurement_repair(line)
|
||||
return True
|
||||
|
||||
def _run_procurement_repair(self, line):
|
||||
procurements = []
|
||||
errors = []
|
||||
procurement = self._prepare_procurement_repair(line)
|
||||
procurements.append(procurement)
|
||||
try:
|
||||
self.env["procurement.group"].run(procurements)
|
||||
except UserError as error:
|
||||
errors.append(error.args[0])
|
||||
if errors:
|
||||
raise UserError("\n".join(errors))
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _get_procurement_data_repair(self, line):
|
||||
warehouse = self.location_id.get_warehouse()
|
||||
if not self.procurement_group_id:
|
||||
group_id = self.env["procurement.group"].create({"name": self.name})
|
||||
self.procurement_group_id = group_id
|
||||
procurement_data = {
|
||||
"name": self.name,
|
||||
"group_id": self.procurement_group_id,
|
||||
"origin": self.name,
|
||||
"date_planned": fields.Datetime.now(),
|
||||
"product_id": line.product_id.id,
|
||||
"product_qty": line.product_uom_qty,
|
||||
"product_uom": line.product_uom.id,
|
||||
"company_id": self.company_id,
|
||||
"warehouse_id": warehouse,
|
||||
"repair_line_id": line.id,
|
||||
}
|
||||
if line.lot_id:
|
||||
procurement_data["lot_id"] = line.lot_id.id
|
||||
if line.type == "remove":
|
||||
procurement_data[
|
||||
"source_repair_location_id"
|
||||
] = line.repair_id.location_id.id
|
||||
return procurement_data
|
||||
|
||||
@api.model
|
||||
def _prepare_procurement_repair(self, line):
|
||||
values = self._get_procurement_data_repair(line)
|
||||
warehouse = self.location_id.get_warehouse()
|
||||
location = (
|
||||
self.location_id
|
||||
if line.type == "add"
|
||||
else warehouse.remove_c_type_id.default_location_dest_id
|
||||
)
|
||||
procurement = self.env["procurement.group"].Procurement(
|
||||
line.product_id,
|
||||
line.product_uom_qty,
|
||||
line.product_uom,
|
||||
location,
|
||||
values.get("origin"),
|
||||
values.get("origin"),
|
||||
self.company_id,
|
||||
values,
|
||||
)
|
||||
return procurement
|
||||
|
||||
def _update_stock_moves_and_picking_state(self):
|
||||
for repair in self:
|
||||
for picking in repair.picking_ids:
|
||||
if picking.location_dest_id.id == self.location_id.id:
|
||||
for move_line in picking.move_ids_without_package:
|
||||
stock_moves = repair.stock_move_ids.filtered(
|
||||
lambda m: m.product_id.id
|
||||
== repair.operations.filtered(
|
||||
lambda l: l.type == "add"
|
||||
and l.product_id.id == m.product_id.id
|
||||
).product_id.id
|
||||
and m.location_id.id == self.location_id.id
|
||||
)
|
||||
if stock_moves:
|
||||
stock_moves[0].write(
|
||||
{
|
||||
"move_orig_ids": [(4, move_line.id)],
|
||||
"state": "waiting",
|
||||
}
|
||||
)
|
||||
if picking.location_id.id == self.location_id.id:
|
||||
for move_line in picking.move_ids_without_package:
|
||||
stock_moves = repair.stock_move_ids.filtered(
|
||||
lambda m: m.product_id.id
|
||||
== repair.operations.filtered(
|
||||
lambda l: l.type == "remove"
|
||||
and l.product_id.id == m.product_id.id
|
||||
).product_id.id
|
||||
and m.location_dest_id.id == self.location_id.id
|
||||
)
|
||||
if stock_moves:
|
||||
move_line.write(
|
||||
{
|
||||
"move_orig_ids": [(4, stock_moves[0].id)],
|
||||
"state": "waiting",
|
||||
}
|
||||
)
|
||||
# We are using write here because
|
||||
# the repair_stock_move module does not use stock rules.
|
||||
# As a result, we manually link the stock moves
|
||||
# and then recompute the state of the picking.
|
||||
picking._compute_state()
|
||||
|
||||
def action_repair_confirm(self):
|
||||
res = super().action_repair_confirm()
|
||||
for repair in self:
|
||||
warehouse = repair.location_id.get_warehouse()
|
||||
if warehouse.repair_steps in ["2_steps", "3_steps"]:
|
||||
repair._action_launch_stock_rule(
|
||||
repair.operations.filtered(lambda l: l.type == "add"),
|
||||
)
|
||||
if warehouse.repair_steps == "3_steps":
|
||||
repair._action_launch_stock_rule(
|
||||
repair.operations.filtered(lambda l: l.type == "remove"),
|
||||
)
|
||||
repair._update_stock_moves_and_picking_state()
|
||||
return res
|
||||
|
||||
@api.onchange("location_id")
|
||||
def _onchange_location_id(self):
|
||||
warehouse = self.location_id.get_warehouse()
|
||||
for line in self.operations:
|
||||
if line.type == "add":
|
||||
line.location_id = self.location_id
|
||||
elif line.type == "remove" and warehouse.repair_steps == "3_steps":
|
||||
line.location_dest_id = self.location_id
|
||||
|
||||
|
||||
class RepairLine(models.Model):
|
||||
_inherit = "repair.line"
|
||||
|
||||
@api.onchange("type", "product_id")
|
||||
def onchange_operation_type(self):
|
||||
super().onchange_operation_type()
|
||||
production_location = self.env["stock.location"].search(
|
||||
[("usage", "=", "production")], limit=1
|
||||
)
|
||||
warehouse = self.repair_id.location_id.get_warehouse()
|
||||
if self.type == "add":
|
||||
self.write(
|
||||
{
|
||||
"location_id": self.repair_id.location_id.id,
|
||||
"location_dest_id": production_location.id,
|
||||
}
|
||||
)
|
||||
elif self.type == "remove":
|
||||
self.write({"location_id": production_location.id})
|
||||
if warehouse.repair_steps in ["1_step", "2_steps"]:
|
||||
scrap_location = self.env["stock.location"].search(
|
||||
[
|
||||
("scrap_location", "=", True),
|
||||
("company_id", "=", warehouse.company_id.id),
|
||||
],
|
||||
limit=1,
|
||||
)
|
||||
self.write({"location_dest_id": scrap_location.id})
|
||||
else:
|
||||
self.write({"location_dest_id": self.repair_id.location_id.id})
|
||||
|
||||
def create(self, vals):
|
||||
line = super().create(vals)
|
||||
if line.repair_id.state in ["confirmed", "under_repair", "ready"]:
|
||||
line.repair_id._action_launch_stock_rule(line)
|
||||
return line
|
||||
27
repair_picking/models/stock_rule.py
Normal file
27
repair_picking/models/stock_rule.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class StockRule(models.Model):
|
||||
_inherit = "stock.rule"
|
||||
|
||||
def _get_custom_move_fields(self):
|
||||
fields = super(StockRule, self)._get_custom_move_fields()
|
||||
# Fields is added on `repair_stock_move` module.
|
||||
fields += ["repair_line_id"]
|
||||
return fields
|
||||
|
||||
|
||||
class ProcurementGroup(models.Model):
|
||||
_inherit = "procurement.group"
|
||||
|
||||
@api.model
|
||||
def _get_rule_domain(self, location, values):
|
||||
domain = super(ProcurementGroup, self)._get_rule_domain(location, values)
|
||||
if values.get("source_repair_location_id"):
|
||||
domain.append(
|
||||
("location_src_id", "=", values.get("source_repair_location_id"))
|
||||
)
|
||||
return domain
|
||||
184
repair_picking/models/stock_warehouse.py
Normal file
184
repair_picking/models/stock_warehouse.py
Normal file
@@ -0,0 +1,184 @@
|
||||
# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class StockWarehouse(models.Model):
|
||||
_inherit = "stock.warehouse"
|
||||
|
||||
repair_steps = fields.Selection(
|
||||
[
|
||||
("1_step", "Repair"),
|
||||
("2_steps", "Pick component, repair"),
|
||||
("3_steps", "Pick component, repair, store removed component"),
|
||||
],
|
||||
string="Repair Steps",
|
||||
default="1_step",
|
||||
)
|
||||
add_c_type_id = fields.Many2one(
|
||||
"stock.picking.type", string="Add Component to Repair"
|
||||
)
|
||||
remove_c_type_id = fields.Many2one(
|
||||
"stock.picking.type", string="Remove component from Repair"
|
||||
)
|
||||
repair_route_id = fields.Many2one("stock.location.route", string="Repair Route")
|
||||
repair_location_id = fields.Many2one("stock.location", string="Repair Location")
|
||||
|
||||
def update_picking_types(self, repair_steps, repair_location_id):
|
||||
if repair_steps in ["2_steps", "3_steps"]:
|
||||
self.add_c_type_id.active = True
|
||||
if repair_steps == "1_step":
|
||||
self.add_c_type_id.active = False
|
||||
if repair_steps == "3_steps":
|
||||
self.remove_c_type_id.active = True
|
||||
if repair_steps in ["1_step", "2_steps"]:
|
||||
self.remove_c_type_id.active = False
|
||||
if repair_location_id:
|
||||
self.add_c_type_id.write({"default_location_dest_id": repair_location_id})
|
||||
self.remove_c_type_id.write({"default_location_src_id": repair_location_id})
|
||||
|
||||
def update_repair_routes(self, repair_steps, repair_location_id):
|
||||
if repair_steps == "2_steps" or repair_steps == "3_steps":
|
||||
self.repair_route_id.active = True
|
||||
existing_rule = (
|
||||
self.env["stock.rule"]
|
||||
.with_context(active_test=False)
|
||||
.search(
|
||||
[
|
||||
("picking_type_id", "=", self.add_c_type_id.id),
|
||||
("route_id", "=", self.repair_route_id.id),
|
||||
],
|
||||
limit=1,
|
||||
)
|
||||
)
|
||||
existing_rule.active = True
|
||||
if repair_steps == "1_step":
|
||||
for rule in self.repair_route_id.rule_ids:
|
||||
rule.active = False
|
||||
self.repair_route_id.active = False
|
||||
if repair_location_id:
|
||||
self.repair_route_id.rule_ids.filtered(
|
||||
lambda r: r.picking_type_id == self.add_c_type_id
|
||||
).write({"location_id": repair_location_id})
|
||||
self.repair_route_id.rule_ids.filtered(
|
||||
lambda r: r.picking_type_id == self.remove_c_type_id
|
||||
).write({"location_src_id": repair_location_id})
|
||||
if repair_steps in ["1_step", "2_steps"]:
|
||||
self.repair_route_id.rule_ids.filtered(
|
||||
lambda r: r.picking_type_id == self.remove_c_type_id
|
||||
).active = False
|
||||
|
||||
def write(self, vals):
|
||||
res = super(StockWarehouse, self).write(vals)
|
||||
for warehouse in self:
|
||||
repair_steps = vals.get("repair_steps")
|
||||
repair_location_id = vals.get("repair_location_id")
|
||||
if repair_steps:
|
||||
if repair_steps in ["3_steps", "2_steps"]:
|
||||
warehouse._create_repair_picking_types()
|
||||
warehouse._create_repair_route()
|
||||
if repair_steps == "3_steps":
|
||||
warehouse._create_remove_rule()
|
||||
if repair_steps or repair_location_id:
|
||||
warehouse.update_picking_types(repair_steps, repair_location_id)
|
||||
warehouse.update_repair_routes(repair_steps, repair_location_id)
|
||||
return res
|
||||
|
||||
def _create_repair_picking_types(self):
|
||||
for warehouse in self:
|
||||
repair_location_id = (
|
||||
warehouse.repair_location_id.id or warehouse.lot_stock_id.id
|
||||
)
|
||||
if not warehouse.add_c_type_id:
|
||||
pbr_type = self.env["stock.picking.type"].create(
|
||||
{
|
||||
"name": "Add Component to Repair",
|
||||
"code": "internal",
|
||||
"sequence_code": "ACR",
|
||||
"warehouse_id": warehouse.id,
|
||||
"default_location_src_id": warehouse.lot_stock_id.id,
|
||||
"default_location_dest_id": repair_location_id,
|
||||
"company_id": warehouse.company_id.id,
|
||||
}
|
||||
)
|
||||
warehouse.add_c_type_id = pbr_type.id
|
||||
else:
|
||||
warehouse.add_c_type_id.write(
|
||||
{"default_location_dest_id": repair_location_id}
|
||||
)
|
||||
if not warehouse.remove_c_type_id:
|
||||
par_type = self.env["stock.picking.type"].create(
|
||||
{
|
||||
"name": "Remove component from Repair",
|
||||
"code": "internal",
|
||||
"sequence_code": "RCR",
|
||||
"warehouse_id": warehouse.id,
|
||||
"default_location_src_id": repair_location_id,
|
||||
"default_location_dest_id": warehouse.view_location_id.id,
|
||||
"company_id": warehouse.company_id.id,
|
||||
}
|
||||
)
|
||||
warehouse.remove_c_type_id = par_type.id
|
||||
else:
|
||||
warehouse.remove_c_type_id.write(
|
||||
{"default_location_src_id": repair_location_id}
|
||||
)
|
||||
|
||||
def _create_repair_route(self):
|
||||
for warehouse in self:
|
||||
if not warehouse.repair_route_id:
|
||||
route = self.env["stock.location.route"].create(
|
||||
{
|
||||
"name": "Repair Route for %s" % warehouse.name,
|
||||
"warehouse_selectable": True,
|
||||
"product_selectable": False,
|
||||
"warehouse_ids": [(6, 0, warehouse.ids)],
|
||||
"company_id": warehouse.company_id.id,
|
||||
}
|
||||
)
|
||||
warehouse.repair_route_id = route.id
|
||||
self.env["stock.rule"].create(
|
||||
{
|
||||
"name": "Add Component to Repair",
|
||||
"picking_type_id": warehouse.add_c_type_id.id,
|
||||
"route_id": route.id,
|
||||
"location_src_id": warehouse.lot_stock_id.id,
|
||||
"location_id": warehouse.repair_location_id.id
|
||||
or warehouse.view_location_id.id,
|
||||
"action": "pull",
|
||||
"company_id": warehouse.company_id.id,
|
||||
"warehouse_id": warehouse.id,
|
||||
}
|
||||
)
|
||||
|
||||
def _create_remove_rule(self):
|
||||
for warehouse in self:
|
||||
existing_rule = (
|
||||
self.env["stock.rule"]
|
||||
.with_context(active_test=False)
|
||||
.search(
|
||||
[
|
||||
("picking_type_id", "=", warehouse.remove_c_type_id.id),
|
||||
("route_id", "=", warehouse.repair_route_id.id),
|
||||
],
|
||||
limit=1,
|
||||
)
|
||||
)
|
||||
if not existing_rule:
|
||||
self.env["stock.rule"].create(
|
||||
{
|
||||
"name": "Remove component from Repair",
|
||||
"picking_type_id": warehouse.remove_c_type_id.id,
|
||||
"route_id": warehouse.repair_route_id.id,
|
||||
"location_src_id": warehouse.repair_location_id.id
|
||||
or warehouse.view_location_id.id,
|
||||
"location_id": warehouse.view_location_id.id,
|
||||
"action": "pull",
|
||||
"company_id": warehouse.company_id.id,
|
||||
"warehouse_id": warehouse.id,
|
||||
"active": True,
|
||||
}
|
||||
)
|
||||
else:
|
||||
existing_rule.active = True
|
||||
1
repair_picking/readme/CONTRIBUTORS.rst
Normal file
1
repair_picking/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1 @@
|
||||
* Joan Sisquella <joan.sisquella@forgeflow.com>
|
||||
12
repair_picking/readme/DESCRIPTION.rst
Normal file
12
repair_picking/readme/DESCRIPTION.rst
Normal file
@@ -0,0 +1,12 @@
|
||||
Repair Management Extension for Odoo
|
||||
=====================================
|
||||
|
||||
This module extends the repair management functionality in Odoo, providing additional options for handling the repair process in a more customizable and efficient way. It allows the configuration of repair steps based on specific business requirements and adds new picking types for managing component addition and removal in repair orders.
|
||||
|
||||
Main Features:
|
||||
--------------
|
||||
- Customize repair steps: Choose between a 1-step, 2-step, or 3-step repair process.
|
||||
- Add and remove components during the repair process using separate picking types.
|
||||
- Associate repair orders with pickings for improved traceability.
|
||||
- Automatic creation of pickings and procurement routes based on the selected repair steps.
|
||||
- Manage repair locations and routes more efficiently with warehouse settings.
|
||||
23
repair_picking/readme/USAGE.rst
Normal file
23
repair_picking/readme/USAGE.rst
Normal file
@@ -0,0 +1,23 @@
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
1. Install the module in your Odoo instance.
|
||||
|
||||
2. Navigate to Inventory > Configuration > Warehouses, and select a warehouse.
|
||||
|
||||
3. In the "Repair Steps" field, choose between "Repair", "Pick component, repair", or "Pick component, repair, store removed component" to define the repair process.
|
||||
|
||||
4. Define the "Repair Location", "Add Component to Repair" picking type, "Remove component from Repair" picking type, and "Repair Route" as needed.
|
||||
|
||||
Using the Repair Management Extension
|
||||
=====================================
|
||||
|
||||
1. Navigate to Repair > Repair Orders and create a new repair order.
|
||||
|
||||
2. In the Operations tab, add components to be added or removed during the repair process.
|
||||
|
||||
3. Confirm the repair order. This will automatically generate the necessary pickings based on the configured repair steps.
|
||||
|
||||
4. Process the pickings as required during the repair process.
|
||||
|
||||
5. If the repair order needs to be canceled, all associated pickings that are not in "cancel" or "done" state will also be canceled automatically.
|
||||
2
repair_picking/tests/__init__.py
Normal file
2
repair_picking/tests/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import test_stock_repair_warehouse
|
||||
from . import test_stock_repair_order
|
||||
266
repair_picking/tests/test_stock_repair_order.py
Normal file
266
repair_picking/tests/test_stock_repair_order.py
Normal file
@@ -0,0 +1,266 @@
|
||||
# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
class TestStockRepairOrder(common.SavepointCase):
|
||||
@classmethod
|
||||
def setUp(cls):
|
||||
super(TestStockRepairOrder, cls).setUpClass()
|
||||
|
||||
cls.repair_model = cls.env["repair.order"]
|
||||
cls.repair_line_model = cls.env["repair.line"]
|
||||
cls.product_model = cls.env["product.product"]
|
||||
cls.stock_location_model = cls.env["stock.location"]
|
||||
cls.warehouse_model = cls.env["stock.warehouse"]
|
||||
cls.company = cls.env["res.company"].create(
|
||||
{
|
||||
"name": "My Test Company",
|
||||
}
|
||||
)
|
||||
cls.warehouse = cls.warehouse_model.create(
|
||||
{
|
||||
"name": "Test Warehouse",
|
||||
"code": "TW",
|
||||
"company_id": cls.company.id,
|
||||
}
|
||||
)
|
||||
|
||||
cls.product1 = cls.product_model.create(
|
||||
{
|
||||
"name": "Product 1",
|
||||
"type": "product",
|
||||
"company_id": cls.company.id,
|
||||
}
|
||||
)
|
||||
cls.product2 = cls.product_model.create(
|
||||
{
|
||||
"name": "Product 2",
|
||||
"type": "product",
|
||||
"company_id": cls.company.id,
|
||||
}
|
||||
)
|
||||
cls.repair_location = cls.stock_location_model.create(
|
||||
{
|
||||
"name": "Repair Location",
|
||||
"usage": "internal",
|
||||
"location_id": cls.warehouse.view_location_id.id,
|
||||
"company_id": cls.company.id,
|
||||
}
|
||||
)
|
||||
cls.production_location = cls.stock_location_model.create(
|
||||
{
|
||||
"name": "Production Location",
|
||||
"usage": "production",
|
||||
"company_id": cls.company.id,
|
||||
}
|
||||
)
|
||||
cls.env["stock.quant"].create(
|
||||
{
|
||||
"product_id": cls.product1.id,
|
||||
"location_id": cls.repair_location.id,
|
||||
"quantity": 10,
|
||||
}
|
||||
)
|
||||
cls.env["stock.quant"].create(
|
||||
{
|
||||
"product_id": cls.product2.id,
|
||||
"location_id": cls.warehouse.lot_stock_id.id,
|
||||
"quantity": 10,
|
||||
}
|
||||
)
|
||||
|
||||
def test_1step_repair_order_flow(self):
|
||||
self.warehouse.write(
|
||||
{
|
||||
"repair_steps": "1_step",
|
||||
"repair_location_id": self.repair_location.id,
|
||||
}
|
||||
)
|
||||
repair_order = self.repair_model.create(
|
||||
{
|
||||
"product_id": self.product1.id,
|
||||
"product_uom": self.product1.uom_id.id,
|
||||
"location_id": self.repair_location.id,
|
||||
"company_id": self.company.id,
|
||||
}
|
||||
)
|
||||
self.repair_line_model.create(
|
||||
{
|
||||
"name": "Repair Line 1",
|
||||
"repair_id": repair_order.id,
|
||||
"product_id": self.product2.id,
|
||||
"type": "add",
|
||||
"product_uom_qty": 1,
|
||||
"product_uom": self.product2.uom_id.id,
|
||||
"price_unit": 1,
|
||||
"location_id": self.repair_location.id,
|
||||
"location_dest_id": self.production_location.id,
|
||||
}
|
||||
)
|
||||
repair_order.action_repair_confirm()
|
||||
self.assertEqual(repair_order.state, "confirmed")
|
||||
repair_order.action_repair_ready()
|
||||
self.assertEqual(repair_order.state, "ready")
|
||||
|
||||
def test_2steps_repair_order_flow(self):
|
||||
self.warehouse.write(
|
||||
{
|
||||
"repair_steps": "2_steps",
|
||||
"repair_location_id": self.repair_location.id,
|
||||
}
|
||||
)
|
||||
self.product2.write(
|
||||
{"route_ids": [(6, 0, [self.warehouse.repair_route_id.id])]}
|
||||
)
|
||||
repair_order = self.repair_model.create(
|
||||
{
|
||||
"product_id": self.product1.id,
|
||||
"product_uom": self.product1.uom_id.id,
|
||||
"location_id": self.repair_location.id,
|
||||
"company_id": self.company.id,
|
||||
}
|
||||
)
|
||||
self.repair_line_model.create(
|
||||
{
|
||||
"name": "Repair Line 2",
|
||||
"repair_id": repair_order.id,
|
||||
"product_id": self.product2.id,
|
||||
"type": "add",
|
||||
"product_uom_qty": 1,
|
||||
"product_uom": self.product2.uom_id.id,
|
||||
"price_unit": 1,
|
||||
"location_id": self.repair_location.id,
|
||||
"location_dest_id": self.production_location.id,
|
||||
}
|
||||
)
|
||||
repair_order.action_repair_confirm()
|
||||
self.assertEqual(repair_order.state, "confirmed")
|
||||
self.assertTrue(repair_order.picking_ids)
|
||||
self.assertEqual(len(repair_order.picking_ids), 1)
|
||||
|
||||
def test_3steps_repair_order_flow(self):
|
||||
self.warehouse.write(
|
||||
{
|
||||
"repair_steps": "3_steps",
|
||||
"repair_location_id": self.repair_location.id,
|
||||
}
|
||||
)
|
||||
self.product2.write(
|
||||
{"route_ids": [(6, 0, [self.warehouse.repair_route_id.id])]}
|
||||
)
|
||||
repair_order = self.repair_model.create(
|
||||
{
|
||||
"product_id": self.product1.id,
|
||||
"product_uom": self.product1.uom_id.id,
|
||||
"location_id": self.repair_location.id,
|
||||
"company_id": self.company.id,
|
||||
}
|
||||
)
|
||||
self.repair_line_model.create(
|
||||
{
|
||||
"name": "Repair Line 3",
|
||||
"repair_id": repair_order.id,
|
||||
"product_id": self.product2.id,
|
||||
"type": "add",
|
||||
"product_uom_qty": 1,
|
||||
"product_uom": self.product2.uom_id.id,
|
||||
"price_unit": 1,
|
||||
"location_id": self.repair_location.id,
|
||||
"location_dest_id": self.production_location.id,
|
||||
}
|
||||
)
|
||||
self.repair_line_model.create(
|
||||
{
|
||||
"name": "Repair Line 4",
|
||||
"repair_id": repair_order.id,
|
||||
"product_id": self.product2.id,
|
||||
"type": "remove",
|
||||
"product_uom_qty": 1,
|
||||
"product_uom": self.product2.uom_id.id,
|
||||
"price_unit": 1,
|
||||
"location_id": self.production_location.id,
|
||||
"location_dest_id": self.repair_location.id,
|
||||
}
|
||||
)
|
||||
repair_order.action_repair_confirm()
|
||||
self.assertEqual(repair_order.state, "confirmed")
|
||||
self.assertTrue(repair_order.picking_ids)
|
||||
self.assertEqual(len(repair_order.picking_ids), 2)
|
||||
repair_order.action_repair_cancel()
|
||||
self.assertEqual(repair_order.state, "cancel")
|
||||
for picking in repair_order.picking_ids:
|
||||
self.assertEqual(picking.state, "cancel")
|
||||
|
||||
def test_update_related_pickings(self):
|
||||
self.warehouse.write(
|
||||
{
|
||||
"repair_steps": "3_steps",
|
||||
"repair_location_id": self.repair_location.id,
|
||||
}
|
||||
)
|
||||
self.product2.write(
|
||||
{"route_ids": [(6, 0, [self.warehouse.repair_route_id.id])]}
|
||||
)
|
||||
repair_order = self.repair_model.create(
|
||||
{
|
||||
"product_id": self.product1.id,
|
||||
"product_uom": self.product1.uom_id.id,
|
||||
"location_id": self.repair_location.id,
|
||||
"company_id": self.company.id,
|
||||
}
|
||||
)
|
||||
self.repair_line_model.create(
|
||||
{
|
||||
"name": "Repair Line 3",
|
||||
"repair_id": repair_order.id,
|
||||
"product_id": self.product2.id,
|
||||
"type": "add",
|
||||
"product_uom_qty": 1,
|
||||
"product_uom": self.product2.uom_id.id,
|
||||
"price_unit": 1,
|
||||
"location_id": self.repair_location.id,
|
||||
"location_dest_id": self.production_location.id,
|
||||
}
|
||||
)
|
||||
repair_order.action_repair_confirm()
|
||||
self.assertEqual(repair_order.state, "confirmed")
|
||||
self.assertTrue(repair_order.picking_ids)
|
||||
self.assertEqual(len(repair_order.picking_ids), 1)
|
||||
self.assertEqual(len(repair_order.picking_ids.move_ids_without_package), 1)
|
||||
self.assertEqual(
|
||||
repair_order.picking_ids.move_ids_without_package.product_uom_qty, 1
|
||||
)
|
||||
self.repair_line_model.create(
|
||||
{
|
||||
"name": "Repair Line Add",
|
||||
"repair_id": repair_order.id,
|
||||
"product_id": self.product2.id,
|
||||
"type": "add",
|
||||
"product_uom_qty": 1,
|
||||
"product_uom": self.product2.uom_id.id,
|
||||
"price_unit": 1,
|
||||
"location_id": self.repair_location.id,
|
||||
"location_dest_id": self.production_location.id,
|
||||
}
|
||||
)
|
||||
self.assertEqual(len(repair_order.picking_ids), 1)
|
||||
self.assertEqual(len(repair_order.picking_ids.move_ids_without_package), 1)
|
||||
self.assertEqual(
|
||||
repair_order.picking_ids.move_ids_without_package.product_uom_qty, 2
|
||||
)
|
||||
self.repair_line_model.create(
|
||||
{
|
||||
"name": "Repair Line Remove",
|
||||
"repair_id": repair_order.id,
|
||||
"product_id": self.product2.id,
|
||||
"type": "remove",
|
||||
"product_uom_qty": 1,
|
||||
"product_uom": self.product2.uom_id.id,
|
||||
"price_unit": 1,
|
||||
"location_id": self.production_location.id,
|
||||
"location_dest_id": self.repair_location.id,
|
||||
}
|
||||
)
|
||||
self.assertEqual(len(repair_order.picking_ids), 2)
|
||||
116
repair_picking/tests/test_stock_repair_warehouse.py
Normal file
116
repair_picking/tests/test_stock_repair_warehouse.py
Normal file
@@ -0,0 +1,116 @@
|
||||
# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
class TestStockRepairWarehouse(common.SavepointCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestStockRepairWarehouse, cls).setUpClass()
|
||||
|
||||
cls.warehouse_obj = cls.env["stock.warehouse"]
|
||||
cls.product_obj = cls.env["product.product"]
|
||||
|
||||
cls.product_1 = cls.product_obj.create(
|
||||
{
|
||||
"name": "Product 1",
|
||||
"type": "product",
|
||||
}
|
||||
)
|
||||
|
||||
cls.warehouse_1 = cls.warehouse_obj.create(
|
||||
{
|
||||
"name": "Test Warehouse",
|
||||
"code": "TWH",
|
||||
"repair_steps": "1_step",
|
||||
}
|
||||
)
|
||||
|
||||
def test_01_warehouse_creation(self):
|
||||
self.assertEqual(self.warehouse_1.repair_steps, "1_step")
|
||||
|
||||
def test_02_update_repair_steps(self):
|
||||
self.warehouse_1.repair_steps = "2_steps"
|
||||
|
||||
self.assertEqual(self.warehouse_1.repair_steps, "2_steps")
|
||||
self.assertTrue(self.warehouse_1.add_c_type_id.active)
|
||||
self.assertFalse(self.warehouse_1.remove_c_type_id.active)
|
||||
self.assertTrue(self.warehouse_1.repair_route_id.active)
|
||||
|
||||
def test_03_update_repair_steps_to_3_steps(self):
|
||||
self.warehouse_1.repair_steps = "3_steps"
|
||||
|
||||
self.assertEqual(self.warehouse_1.repair_steps, "3_steps")
|
||||
self.assertTrue(self.warehouse_1.add_c_type_id.active)
|
||||
self.assertTrue(self.warehouse_1.remove_c_type_id.active)
|
||||
self.assertTrue(self.warehouse_1.repair_route_id.active)
|
||||
|
||||
def test_04_reverse_and_update_repair_steps(self):
|
||||
self.warehouse_1.repair_steps = "1_step"
|
||||
self.warehouse_1.repair_steps = "2_steps"
|
||||
self.assertEqual(self.warehouse_1.repair_steps, "2_steps")
|
||||
self.assertTrue(self.warehouse_1.add_c_type_id.active)
|
||||
self.assertFalse(self.warehouse_1.remove_c_type_id.active)
|
||||
self.assertTrue(self.warehouse_1.repair_route_id.active)
|
||||
add_rule = self.env["stock.rule"].search(
|
||||
[
|
||||
("picking_type_id", "=", self.warehouse_1.add_c_type_id.id),
|
||||
("route_id", "=", self.warehouse_1.repair_route_id.id),
|
||||
]
|
||||
)
|
||||
self.assertTrue(add_rule.active)
|
||||
|
||||
remove_rule = self.env["stock.rule"].search(
|
||||
[
|
||||
("picking_type_id", "=", self.warehouse_1.remove_c_type_id.id),
|
||||
("route_id", "=", self.warehouse_1.repair_route_id.id),
|
||||
]
|
||||
)
|
||||
self.assertFalse(remove_rule)
|
||||
|
||||
self.warehouse_1.repair_steps = "3_steps"
|
||||
self.assertEqual(self.warehouse_1.repair_steps, "3_steps")
|
||||
self.assertTrue(self.warehouse_1.add_c_type_id.active)
|
||||
self.assertTrue(self.warehouse_1.remove_c_type_id.active)
|
||||
self.assertTrue(self.warehouse_1.repair_route_id.active)
|
||||
add_rule = self.env["stock.rule"].search(
|
||||
[
|
||||
("picking_type_id", "=", self.warehouse_1.add_c_type_id.id),
|
||||
("route_id", "=", self.warehouse_1.repair_route_id.id),
|
||||
]
|
||||
)
|
||||
self.assertTrue(add_rule.active)
|
||||
remove_rule = self.env["stock.rule"].search(
|
||||
[
|
||||
("picking_type_id", "=", self.warehouse_1.remove_c_type_id.id),
|
||||
("route_id", "=", self.warehouse_1.repair_route_id.id),
|
||||
]
|
||||
)
|
||||
self.assertTrue(remove_rule.active)
|
||||
|
||||
self.warehouse_1.repair_steps = "2_steps"
|
||||
self.assertEqual(self.warehouse_1.repair_steps, "2_steps")
|
||||
self.assertTrue(self.warehouse_1.add_c_type_id.active)
|
||||
self.assertFalse(self.warehouse_1.remove_c_type_id.active)
|
||||
self.assertTrue(self.warehouse_1.repair_route_id.active)
|
||||
add_rule = self.env["stock.rule"].search(
|
||||
[
|
||||
("picking_type_id", "=", self.warehouse_1.add_c_type_id.id),
|
||||
("route_id", "=", self.warehouse_1.repair_route_id.id),
|
||||
]
|
||||
)
|
||||
self.assertTrue(add_rule.active)
|
||||
|
||||
remove_rule = self.env["stock.rule"].search(
|
||||
[
|
||||
("picking_type_id", "=", self.warehouse_1.remove_c_type_id.id),
|
||||
("route_id", "=", self.warehouse_1.repair_route_id.id),
|
||||
]
|
||||
)
|
||||
self.assertFalse(remove_rule)
|
||||
|
||||
self.warehouse_1.repair_steps = "1_step"
|
||||
self.assertFalse(self.warehouse_1.add_c_type_id.active)
|
||||
self.assertFalse(self.warehouse_1.remove_c_type_id.active)
|
||||
self.assertFalse(self.warehouse_1.repair_route_id.active)
|
||||
22
repair_picking/views/repair_order_view.xml
Normal file
22
repair_picking/views/repair_order_view.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="view_repair_order_form_repair_stock_move_inherited" model="ir.ui.view">
|
||||
<field name="name">repair.order.form - repair_stock_move - custom</field>
|
||||
<field name="model">repair.order</field>
|
||||
<field name="inherit_id" ref="repair_stock_move.view_repair_order_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('oe_button_box')]" position="inside">
|
||||
<button
|
||||
name="action_view_pickings"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-truck"
|
||||
attrs="{'invisible': [('picking_count', '=', 0)]}"
|
||||
help="Transfers associated with this repair order"
|
||||
>
|
||||
<field name="picking_count" widget="statinfo" string="Transfers" />
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
26
repair_picking/views/stock_warehouse_views.xml
Normal file
26
repair_picking/views/stock_warehouse_views.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="view_warehouse_inherit_repair" model="ir.ui.view">
|
||||
<field name="name">Stock Warehouse Inherit Repair</field>
|
||||
<field name="model">stock.warehouse</field>
|
||||
<field name="inherit_id" ref="stock.view_warehouse" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='resupply_wh_ids']" position="before">
|
||||
<field name="repair_steps" widget="radio" />
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='wh_output_stock_loc_id']" position="after">
|
||||
<field name="repair_location_id" />
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='out_type_id']" position="after">
|
||||
<field
|
||||
name="add_c_type_id"
|
||||
attrs="{'invisible': [('repair_steps', 'not in', ['2_steps', '3_steps'])]}"
|
||||
/>
|
||||
<field
|
||||
name="remove_c_type_id"
|
||||
attrs="{'invisible': [('repair_steps', '!=', '3_steps')]}"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
1
setup/repair_picking/odoo/addons/repair_picking
Symbolic link
1
setup/repair_picking/odoo/addons/repair_picking
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../repair_picking
|
||||
6
setup/repair_picking/setup.py
Normal file
6
setup/repair_picking/setup.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
||||
Reference in New Issue
Block a user