Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 40 additions & 19 deletions repair_order_group/README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

==================
Repair Order Group
==================
Expand All @@ -17,7 +13,7 @@ Repair Order Group
.. |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/license-AGPL--3-blue.png
.. |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%2Frepair-lightgray.png?logo=github
Expand All @@ -32,18 +28,27 @@ Repair Order Group

|badge1| |badge2| |badge3| |badge4| |badge5|

This module allows to group several repair orders, managing them in a
more convenient way. Such orders are synchronised in the following
actions:
This module allows users to group several repair orders and manage them
in a more convenient way.

Grouped repair orders are synchronized for the following actions:

- **Partner selection**: when a partner is changed in one repair order,
the same partner is set on all other repair orders from the same
group.
- **Order confirmation**: when one repair order is confirmed, all other
repair orders from the same group are confirmed as well.
- **Order cancellation**: when one repair order is cancelled, all other
repair orders from the same group are cancelled as well.
- **Quotation creation**: when a quotation is created, all repair orders
from the same group are added to the same quotation.

The module also allows configuring the repair order states where the
**Add Grouped Repair** button is available. This is configured with the
**Available in** tags field in the Repair settings.

- Partner selection. When a partner is updated in one of the group
orders the same partner is set in all other orders of the same group.
- Order confirmation. When one of the orders is confirmed all other
orders of the same group are confirmed.
- Order cancellation. When one of the orders is cancelled all other
orders of the same group are cancelled.
- Quotation creation. When a new quotation is created all orders of the
same group are added to the same quotation.
The button is always hidden when the related sales order is confirmed or
cancelled.

**Table of contents**

Expand All @@ -67,9 +72,24 @@ order form.
Usage
=====

Create the first repair order. Click the "Add Another Repair" button in
the header. A new order will be created with the same partner and same
order group.
Create the first repair order.

Click the **Add Grouped Repair** button in the header. A new repair
order will be created with the same partner and the same repair order
group.

The availability of the **Add Grouped Repair** button can be configured
from **Repair > Configuration > Settings** in the **Add Grouped Repair**
setting.

Use the **Available in** field to select the repair order states where
the button should be available.

By default, the button is available in the **New** repair order state.
Additional states can be added in the **Available in** field.

The button is always hidden when the related sales order is confirmed or
cancelled.

Bug Tracker
===========
Expand All @@ -96,6 +116,7 @@ Cetmix <cetmix.com>

- Ivan Sokolov
- Loukachov Andrei
- Dmitry Meita

Maintainers
-----------
Expand Down
3 changes: 3 additions & 0 deletions repair_order_group/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
"website": "https://github.com/OCA/repair",
"depends": [
"repair",
"base_repair_config",
],
"data": [
"data/res_company_data.xml",
"data/repair_order_group_data.xml",
"security/ir.model.access.csv",
"views/repair_order_group_views.xml",
"views/repair_order_views.xml",
"views/res_config_settings_views.xml",
],
"demo": [
"demo/repair_order_group_demo.xml",
Expand Down
8 changes: 8 additions & 0 deletions repair_order_group/data/res_company_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<odoo noupdate="1">
<record id="base.main_company" model="res.company">
<field
name="add_grouped_repair_state_ids"
search="[('field_id.model', '=', 'repair.order'), ('field_id.name', '=', 'state'), ('value', '=', 'draft')]"
/>
</record>
</odoo>
4 changes: 3 additions & 1 deletion repair_order_group/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Copyright (C) 2025 Cetmix OÜ
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import repair_order_group
from . import res_company
from . import repair_order
from . import repair_order_group
from . import res_config_settings
74 changes: 69 additions & 5 deletions repair_order_group/models/repair_order.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (C) 2025 Cetmix OÜ
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import _, api, fields, models
from odoo import api, fields, models
from odoo.exceptions import UserError


Expand Down Expand Up @@ -35,6 +35,29 @@ class RepairOrder(models.Model):
help="All other repairs that belong to the same group.",
)

show_add_grouped_repair = fields.Boolean(
compute="_compute_show_add_grouped_repair",
help="Technical field used to control the Add Grouped Repair button.",
)

def _can_add_grouped_repair(self):
"""Return True if this repair can be added to a repair group."""
self.ensure_one()
allowed_states = set(
self.company_id.sudo().add_grouped_repair_state_ids.mapped("value")
)
return self.state in allowed_states and self.sale_order_id.state not in (
"sale",
"cancel",
)

@api.depends(
"state", "sale_order_id.state", "company_id.add_grouped_repair_state_ids"
)
def _compute_show_add_grouped_repair(self):
for repair in self:
repair.show_add_grouped_repair = repair._can_add_grouped_repair()

@api.depends("group_id", "group_id.repair_ids")
def _compute_grouped_repair_ids(self):
"""Compute other repairs in the same group (excluding current repair)."""
Expand All @@ -53,10 +76,19 @@ def action_add_another_repair(self):
partner, company, and locations, but not copy any parts or products.
"""
self.ensure_one()

if not self._can_add_grouped_repair():
raise UserError(
self.env._(
"You cannot add a grouped repair in the current repair "
"state or when the related sale order is confirmed "
"or cancelled."
)
)

if not self.group_id:
self.group_id = self.env["repair.order.group"].create({})

# Create new empty repair record within the same group
new_repair = self.env["repair.order"].create(
{
"group_id": self.group_id.id,
Expand Down Expand Up @@ -183,7 +215,7 @@ def action_create_sale_order(self):
grouped = self.filtered(lambda r: r.group_id)
ungrouped = self - grouped

# No groups at all fallback to core logic
# No groups at all -> fallback to core logic
if not grouped:
return super().action_create_sale_order()

Expand All @@ -192,7 +224,7 @@ def action_create_sale_order(self):
if concerned:
ref_str = "\n".join(concerned.mapped("name"))
raise UserError(
_(
self.env._(
"You cannot create a quotation for a repair order that is "
"already linked to an existing sale order.\n"
"Concerned repair order(s):\n%(ref_str)s",
Expand All @@ -204,7 +236,7 @@ def action_create_sale_order(self):
if no_partner:
ref_str = "\n".join(no_partner.mapped("name"))
raise UserError(
_(
self.env._(
"You need to define a customer for a repair order in order to "
"create an associated quotation.\n"
"Concerned repair order(s):\n%(ref_str)s",
Expand Down Expand Up @@ -236,6 +268,9 @@ def action_create_sale_order(self):

# Create a separate SO per warehouse ID
for warehouse_id, repairs in repairs_by_warehouse.items():
if repairs._link_to_existing_group_sale_order():
continue

so_vals_list.append(
{
"company_id": _group.company_id.id,
Expand All @@ -258,3 +293,32 @@ def action_create_sale_order(self):

# Return standard action - it will find all SOs linked to repairs
return self.action_view_sale_order()

def _link_to_existing_group_sale_order(self):
"""Link repairs without SO to the existing open group quotation."""
sale_orders = self.sale_order_id.filtered(
lambda so: so.state not in ("sale", "cancel")
)
if not sale_orders:
return False

if len(sale_orders) > 1:
ref_str = "\n".join(self.filtered("sale_order_id").mapped("name"))
raise UserError(
self.env._(
"Several open sale orders are already linked to repair "
"orders from the same repair group and warehouse.\n"
"Concerned repair order(s):\n%(ref_str)s",
ref_str=ref_str,
)
)

repairs_to_add = self.filtered(lambda repair: not repair.sale_order_id)
if repairs_to_add:
sale_orders.write(
{"repair_order_ids": [(4, repair.id) for repair in repairs_to_add]}
)
repairs_to_add.move_ids._create_repair_sale_order_line()
repairs_to_add._post_create_grouped_hook()

return True
37 changes: 37 additions & 0 deletions repair_order_group/models/res_company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright (C) 2025 Cetmix OÜ
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class ResCompany(models.Model):
_inherit = "res.company"

add_grouped_repair_state_ids = fields.Many2many(
comodel_name="ir.model.fields.selection",
relation="res_company_add_grouped_repair_state_rel",
column1="company_id",
column2="selection_id",
string="Add Grouped Repair Available In",
default=lambda self: self._default_add_grouped_repair_state_ids(),
domain=[
("field_id.model", "=", "repair.order"),
("field_id.name", "=", "state"),
],
)

@api.model
def _default_add_grouped_repair_state_ids(self):
"""Return the default state for Add Grouped Repair availability."""
return (
self.env["ir.model.fields.selection"]
.sudo()
.search(
[
("field_id.model", "=", "repair.order"),
("field_id.name", "=", "state"),
("value", "=", "draft"),
],
limit=1,
)
)
15 changes: 15 additions & 0 deletions repair_order_group/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright (C) 2025 Cetmix OÜ
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models


class ResConfigSettings(models.TransientModel):
"""Add repair group settings to the Repair configuration page."""

_inherit = "res.config.settings"

add_grouped_repair_state_ids = fields.Many2many(
related="company_id.add_grouped_repair_state_ids",
readonly=False,
)
1 change: 1 addition & 0 deletions repair_order_group/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Cetmix <cetmix.com>
- Ivan Sokolov
- Loukachov Andrei
- Dmitry Meita
24 changes: 19 additions & 5 deletions repair_order_group/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
This module allows to group several repair orders, managing them in a more convenient way. Such orders are synchronised in the following actions:
This module allows users to group several repair orders and manage them in a
more convenient way.

- Partner selection. When a partner is updated in one of the group orders the same partner is set in all other orders of the same group.
- Order confirmation. When one of the orders is confirmed all other orders of the same group are confirmed.
- Order cancellation. When one of the orders is cancelled all other orders of the same group are cancelled.
- Quotation creation. When a new quotation is created all orders of the same group are added to the same quotation.
Grouped repair orders are synchronized for the following actions:

- **Partner selection**: when a partner is changed in one repair order, the same
partner is set on all other repair orders from the same group.
- **Order confirmation**: when one repair order is confirmed, all other repair
orders from the same group are confirmed as well.
- **Order cancellation**: when one repair order is cancelled, all other repair
orders from the same group are cancelled as well.
- **Quotation creation**: when a quotation is created, all repair orders from
the same group are added to the same quotation.

The module also allows configuring the repair order states where the
**Add Grouped Repair** button is available. This is configured with the
**Available in** tags field in the Repair settings.

The button is always hidden when the related sales order is confirmed or
cancelled.
17 changes: 15 additions & 2 deletions repair_order_group/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
Create the first repair order.
Click the "Add Another Repair" button in the header.
A new order will be created with the same partner and same order group.

Click the **Add Grouped Repair** button in the header. A new repair order will
be created with the same partner and the same repair order group.

The availability of the **Add Grouped Repair** button can be configured from
**Repair > Configuration > Settings** in the **Add Grouped Repair** setting.

Use the **Available in** field to select the repair order states where the
button should be available.

By default, the button is available in the **New** repair order state.
Additional states can be added in the **Available in** field.

The button is always hidden when the related sales order is confirmed or
cancelled.
1 change: 1 addition & 0 deletions repair_order_group/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_repair_order_group_user,Repair Order Group User,model_repair_order_group,stock.group_stock_user,1,1,1,0
access_repair_order_group_manager,Repair Order Group Manager,model_repair_order_group,stock.group_stock_manager,1,1,1,1
access_ir_model_fields_selection_stock_manager,ir.model.fields.selection.stock.manager,base.model_ir_model_fields_selection,stock.group_stock_manager,1,0,0,0
Loading
Loading