[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:
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user