[IMP] repair: updates to repairs workflow

The purpose of this commit is to improve the repairs module general
workflow, to make it more intuitive and more flexible.

1. Change in the way the Repair Reference is created. It is now always
autogenerated. A new Repair Description can be used.
2. Make the 'Parts' and 'Operations' tabs editable up until (and
including) the stage 'Under Repair'.
3. Make the 'Lot/Serial Number' Field non mandatory for tracked
products, up to and including to the stage 'Under Repair'. If the
user forgot to specify the serial number and clicks on 'End Repair',
shows the exisiting error message.
4. Add product moves related to the repair order in a smart button at
the top right of repair, just like for a finished manufacturing order.
5. Add a Sale Order field in the repair form to make it easier to
perform return if needed.
6. When a component is not tracked by serial/lot number, leaves lot/serial
number read only.
7. Separationo of  notes and internal notes into 2 seperate tabs.
8. Lot/serial field always visible but readonly when no tracking.

Task ID-2424420

closes odoo/odoo#66496

Related: odoo/upgrade#2327
Signed-off-by: Rémy Voet <ryv-odoo@users.noreply.github.com>
This commit is contained in:
Nicolas Pierre
2021-04-02 12:26:15 +00:00
parent bdcb3d192b
commit 7f18319f2a
5 changed files with 53 additions and 32 deletions
+1
View File
@@ -27,6 +27,7 @@ The following topics are covered by this module:
'security/repair_security.xml',
'wizard/repair_make_invoice_views.xml',
'wizard/stock_warn_insufficient_qty_views.xml',
'views/stock_move_views.xml',
'views/repair_views.xml',
'report/repair_reports.xml',
'report/repair_templates_repair_order.xml',
+1 -1
View File
@@ -4,7 +4,7 @@
<record id="seq_repair" model="ir.sequence">
<field name="name">Repair Order</field>
<field name="code">repair.order</field>
<field name="prefix">RMA/</field>
<field name="prefix">RO/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
+21 -22
View File
@@ -22,9 +22,10 @@ class Repair(models.Model):
name = fields.Char(
'Repair Reference',
default='/',
default='New',
copy=False, required=True,
states={'confirmed': [('readonly', True)]})
readonly=True)
description = fields.Char('Repair Description')
product_id = fields.Many2one(
'product.product', string='Product to Repair',
domain="[('type', 'in', ['product', 'consu']), '|', ('company_id', '=', company_id), ('company_id', '=', False)]",
@@ -49,16 +50,16 @@ class Repair(models.Model):
state = fields.Selection([
('draft', 'Quotation'),
('confirmed', 'Confirmed'),
('under_repair', 'Under Repair'),
('ready', 'Ready to Repair'),
('under_repair', 'Under Repair'),
('2binvoiced', 'To be Invoiced'),
('invoice_except', 'Invoice Exception'),
('done', 'Repaired'),
('cancel', 'Cancelled')], string='Status',
copy=False, default='draft', readonly=True, tracking=True,
help="* The \'Draft\' status is used when a user is encoding a new and unconfirmed repair order.\n"
"* The \'Confirmed\' status is used when a user confirms the repair order.\n"
"* The \'Ready to Repair\' status is used to start to repairing, user can start repairing only after repair order is confirmed.\n"
"* The \'Under Repair\' status is used when the repair is ongoing.\n"
"* The \'To be Invoiced\' status is used to generate the invoice before or after repairing done.\n"
"* The \'Done\' status is set when repairing is completed.\n"
"* The \'Cancelled\' status is used when user cancel repair order.")
@@ -74,7 +75,7 @@ class Repair(models.Model):
guarantee_limit = fields.Date('Warranty Expiration', states={'confirmed': [('readonly', True)]})
operations = fields.One2many(
'repair.line', 'repair_id', 'Parts',
copy=True, readonly=True, states={'draft': [('readonly', False)]})
copy=True)
pricelist_id = fields.Many2one(
'product.pricelist', 'Pricelist',
default=lambda self: self.env['product.pricelist'].search([('company_id', 'in', [self.env.company.id, False])], limit=1).id,
@@ -98,7 +99,7 @@ class Repair(models.Model):
help="Move created by the repair order")
fees_lines = fields.One2many(
'repair.fee', 'repair_id', 'Operations',
copy=True, readonly=True, states={'draft': [('readonly', False)]})
copy=True, readonly=False)
internal_notes = fields.Text('Internal Notes')
quotation_notes = fields.Text('Quotation Notes')
user_id = fields.Many2one('res.users', string="Responsible", default=lambda self: self.env.user, check_company=True)
@@ -106,6 +107,7 @@ class Repair(models.Model):
'res.company', 'Company',
readonly=True, required=True, index=True,
default=lambda self: self.env.company)
sale_order_id = fields.Many2one('sale.order', 'Sale Order', copy=False, help="Sale Order from which the product to be repaired comes from.")
tag_ids = fields.Many2many('repair.tags', string="Tags")
invoiced = fields.Boolean('Invoiced', copy=False, readonly=True)
repaired = fields.Boolean('Repaired', copy=False, readonly=True)
@@ -206,12 +208,8 @@ class Repair(models.Model):
@api.model
def create(self, vals):
# To avoid consuming a sequence number when clicking on 'Create', we preprend it if the
# the name starts with '/'.
vals['name'] = vals.get('name') or '/'
if vals['name'].startswith('/'):
vals['name'] = (self.env['ir.sequence'].next_by_code('repair.order') or '/') + vals['name']
vals['name'] = vals['name'][:-1] if vals['name'].endswith('/') and vals['name'] != '/' else vals['name']
# We generate a standard reference
vals['name'] = self.env['ir.sequence'].next_by_code('repair.order') or '/'
return super(Repair, self).create(vals)
def button_dummy(self):
@@ -495,6 +493,7 @@ class Repair(models.Model):
"""
if self.filtered(lambda repair: repair.state != 'under_repair'):
raise UserError(_("Repair must be under repair in order to end reparation."))
self._check_product_tracking()
for repair in self:
repair.write({'repaired': True})
vals = {'state': 'done'}
@@ -595,6 +594,15 @@ class Repair(models.Model):
res[repair.id] = move.id
return res
def _check_product_tracking(self):
invalid_lines = self.operations.filtered(lambda x: x.tracking != 'none' and not x.lot_id)
if invalid_lines:
products = invalid_lines.product_id
raise ValidationError(_(
"Serial number is required for operation lines with products: %s",
", ".join(products.mapped('display_name')),
))
class RepairLine(models.Model):
_name = 'repair.line'
@@ -649,16 +657,7 @@ class RepairLine(models.Model):
('cancel', 'Cancelled')], 'Status', default='draft',
copy=False, readonly=True, required=True,
help='The status of a repair line is set automatically to the one of the linked repair order.')
@api.constrains('lot_id', 'product_id')
def _check_product_tracking(self):
invalid_lines = self.filtered(lambda x: x.product_id.tracking != 'none' and not x.lot_id)
if invalid_lines:
products = invalid_lines.product_id
raise ValidationError(_(
"Serial number is required for operation lines with products: %s",
", ".join(products.mapped('name')),
))
tracking = fields.Selection(string='Product Tracking', related="product_id.tracking")
@api.depends('price_unit', 'repair_id', 'product_uom_qty', 'product_id', 'repair_id.invoice_method')
def _compute_price_subtotal(self):
+18 -9
View File
@@ -8,6 +8,7 @@
<field name="arch" type="xml">
<tree string="Repairs order" multi_edit="1" sample="1">
<field name="name"/>
<field name="description"/>
<field name="product_id" readonly="1" optional="show"/>
<field name="product_qty" optional="hide" string="Quantity"/>
<field name="product_uom" string="Unit of Measure" readonly="1" optional="hide"/>
@@ -44,6 +45,7 @@
<sheet string="Repairs order">
<div class="oe_button_box" name="button_box">
<field name="invoice_id" invisible="1"/>
<button name="%(action_repair_move_lines)d" type="action" string="Product Moves" class="oe_stat_button" icon="fa-exchange" attrs="{'invisible': [('state', 'not in', ['done', 'cancel'])]}"/>
<button name="action_created_invoice"
type="object"
class="oe_stat_button"
@@ -56,24 +58,28 @@
</div>
</button>
</div>
<label for="name"/>
<h1>
<field name="name"/>
</h1>
<div class="oe_title">
<label class="o_form_label" for="name"/>
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
<field name="description"/>
<field name="invoice_state" invisible="1"/>
<field name="tracking" invisible="1" attrs="{'readonly': 1}"/>
<field name="product_id" />
<field name="lot_id" context="{'default_product_id': product_id, 'default_company_id': company_id}" groups="stock.group_production_lot" attrs="{'required':[('tracking', 'in', ['serial', 'lot'])], 'invisible': [('tracking', 'not in', ['serial', 'lot'])], 'readonly': ['|', ('state', '=', 'done'), ('tracking', 'not in', ['serial', 'lot'])]}"/>
<field name="product_uom_category_id" invisible="1"/>
<label for="product_qty"/>
<div class="o_row">
<field name="product_qty" attrs="{'readonly':[('tracking', 'in', ['serial'])]}"/>
<field name="product_uom" groups="uom.group_uom"/>
</div>
<field name="lot_id" context="{'default_product_id': product_id, 'default_company_id': company_id}" groups="stock.group_production_lot" attrs="{'required':[('tracking', 'in', ['serial', 'lot'])], 'invisible': [('tracking', 'not in', ['serial', 'lot'])], 'readonly': [('state', '=', 'done')]}"/>
<field name="partner_id" widget="res_partner_many2one" attrs="{'required':[('invoice_method','!=','none')]}" context="{'res_partner_search_mode': 'customer', 'show_vat': True}"/>
<field name="address_id" groups="sale.group_delivery_invoice_address"/>
<field name="sale_order_id"/>
<field name="user_id" domain="[('share', '=', False)]"/>
</group>
<group>
@@ -89,7 +95,7 @@
</group>
<notebook>
<page string="Parts" name="parts">
<field name="operations" context="{'default_product_uom_qty': product_qty, 'default_company_id': company_id}">
<field name="operations" context="{'default_product_uom_qty': product_qty, 'default_company_id': company_id}" attrs="{'readonly':[('state', 'in', ['done', 'cancel'])]}">
<form string="Operations">
<group>
<group>
@@ -126,7 +132,8 @@
<field name="product_id"/>
<field name='name' optional="show"/>
<field name="product_uom_category_id" invisible="1"/>
<field name="lot_id" context="{'default_product_id': product_id, 'default_company_id': company_id}" groups="stock.group_production_lot"/>
<field name="tracking" invisible="1"/>
<field name="lot_id" context="{'default_product_id': product_id, 'default_company_id': company_id}" groups="stock.group_production_lot" attrs="{'readonly':[('tracking', 'not in', ['serial', 'lot'])]}"/>
<field name="location_id" options="{'no_create': True}" groups="stock.group_stock_multi_locations" optional="show"/>
<field name="location_dest_id" options="{'no_create': True}" groups="stock.group_stock_multi_locations" optional="show"/>
<field name="product_uom_qty" string="Quantity"/>
@@ -150,7 +157,7 @@
<div class="oe_clear"/>
</page>
<page string="Operations" name="operations">
<field name="fees_lines" context="{'default_company_id': company_id}">
<field name="fees_lines" context="{'default_company_id': company_id}" attrs="{'readonly':[('state', 'in', ['done', 'cancel'])]}">
<form string="Fees">
<group>
<field name="company_id" invisible="1" force_save="1"/>
@@ -193,8 +200,10 @@
</group>
</group>
</page>
<page string="Notes" name="notes">
<page string="Repair Notes">
<field name="internal_notes" placeholder="Add internal notes."/>
</page>
<page string="Quotation Notes">
<field name="quotation_notes" placeholder="Add quotation notes."/>
</page>
</notebook>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.actions.act_window" id="action_repair_move_lines">
<field name="name">Inventory Moves</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">stock.move.line</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('move_id.repair_id', '=', active_id)]</field>
</record>
</odoo>