UNPKG

bmad-method-odoo

Version:

BMAD-METHOD expansion pack for comprehensive Odoo ERP development workflows

549 lines (455 loc) 18.8 kB
# Enhance Existing Odoo System You are tasked with safely enhancing an existing Odoo system through module inheritance, extension, and integration. This task focuses on brownfield development practices that preserve existing functionality while adding new capabilities. ## Task Objectives ### Primary Goals - Enhance existing Odoo modules without breaking current functionality - Implement new features using inheritance and extension patterns - Ensure upgrade compatibility and maintainability - Follow OCA standards for community compatibility - Minimize system disruption and maintain data integrity ### Success Criteria - New functionality works seamlessly with existing system - All existing functionality remains intact - Enhancement is upgrade-safe and maintainable - Code follows OCA quality standards - Comprehensive testing validates both new and existing features ## Prerequisites ### System Analysis - **Current System Assessment**: Understanding of existing modules and customizations - **Impact Analysis**: Identification of affected areas and dependencies - **Business Requirements**: Clear definition of enhancement objectives - **Technical Constraints**: Database schema limitations and integration points - **User Impact**: Changes to user workflows and training requirements ### Development Environment - Access to production-like environment for testing - Complete backup of current system - Version control system for tracking changes - Testing procedures for regression validation ## Enhancement Strategies ### 1. Model Extension (Field Addition) #### Adding Fields to Existing Models ```python # models/sale_order_extension.py from odoo import api, fields, models class SaleOrderExtension(models.Model): _inherit = 'sale.order' # Add new fields custom_reference = fields.Char( string='Custom Reference', help='Custom reference for internal tracking' ) special_instructions = fields.Text( string='Special Instructions', help='Special handling instructions for this order' ) approval_required = fields.Boolean( string='Approval Required', compute='_compute_approval_required', store=True, help='Indicates if order requires management approval' ) approval_status = fields.Selection([ ('pending', 'Pending Approval'), ('approved', 'Approved'), ('rejected', 'Rejected') ], string='Approval Status', default='pending') @api.depends('amount_total', 'partner_id.approval_limit') def _compute_approval_required(self): for order in self: limit = order.partner_id.approval_limit or 1000.0 order.approval_required = order.amount_total > limit ``` #### Extending Views ```xml <?xml version="1.0" encoding="utf-8"?> <odoo> <!-- Extend existing form view --> <record id="view_order_form_extension" model="ir.ui.view"> <field name="name">sale.order.form.extension</field> <field name="model">sale.order</field> <field name="inherit_id" ref="sale.view_order_form"/> <field name="arch" type="xml"> <!-- Add fields to existing group --> <xpath expr="//field[@name='client_order_ref']" position="after"> <field name="custom_reference"/> </xpath> <!-- Add new page to notebook --> <xpath expr="//notebook" position="inside"> <page string="Special Instructions" name="special_instructions"> <group> <field name="special_instructions" nolabel="1"/> </group> </page> </xpath> <!-- Add approval section in header --> <xpath expr="//header" position="inside"> <button name="action_request_approval" type="object" string="Request Approval" class="oe_highlight" invisible="not approval_required or approval_status != 'pending'"/> <button name="action_approve" type="object" string="Approve" class="oe_highlight" invisible="approval_status != 'pending'" groups="sales_team.group_sale_manager"/> <field name="approval_status" widget="statusbar" invisible="not approval_required"/> </xpath> </field> </record> <!-- Extend tree view --> <record id="view_quotation_tree_extension" model="ir.ui.view"> <field name="name">sale.order.tree.extension</field> <field name="model">sale.order</field> <field name="inherit_id" ref="sale.view_quotation_tree"/> <field name="arch" type="xml"> <xpath expr="//field[@name='amount_total']" position="after"> <field name="approval_status" invisible="not approval_required"/> </xpath> </field> </record> </odoo> ``` ### 2. Business Logic Extension #### Method Override and Extension ```python class SaleOrderLogicExtension(models.Model): _inherit = 'sale.order' @api.model def create(self, vals): # Custom logic before creation if vals.get('partner_id'): partner = self.env['res.partner'].browse(vals['partner_id']) if partner.requires_special_handling: vals['special_instructions'] = 'Handle with care - VIP customer' # Call parent method order = super().create(vals) # Custom logic after creation if order.approval_required: order._send_approval_notification() return order def write(self, vals): # Custom logic before update if 'state' in vals and vals['state'] == 'sale': for order in self: if order.approval_required and order.approval_status != 'approved': raise UserError(_('Cannot confirm order without approval')) # Call parent method result = super().write(vals) # Custom logic after update if vals.get('approval_status') == 'approved': self._send_approval_confirmation() return result def action_confirm(self): # Additional validation before confirmation for order in self: if order.approval_required and order.approval_status != 'approved': raise UserError(_('Order requires approval before confirmation')) # Custom business rules if order.amount_total > 50000 and not order.special_instructions: raise UserError(_('Large orders require special instructions')) # Call parent method return super().action_confirm() def _send_approval_notification(self): """Send notification for approval request""" template = self.env.ref('custom_module.approval_request_template') for order in self: template.send_mail(order.id, force_send=True) def _send_approval_confirmation(self): """Send notification when approved""" template = self.env.ref('custom_module.approval_confirmation_template') for order in self: template.send_mail(order.id, force_send=True) ``` ### 3. Workflow Enhancement #### Adding Custom Actions ```python class SaleOrderWorkflow(models.Model): _inherit = 'sale.order' def action_request_approval(self): """Request approval for high-value orders""" self.ensure_one() if not self.approval_required: raise UserError(_('This order does not require approval')) self.approval_status = 'pending' self._send_approval_notification() return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'message': _('Approval request sent to management'), 'type': 'success', } } def action_approve(self): """Approve the order""" self.ensure_one() if not self.env.user.has_group('sales_team.group_sale_manager'): raise AccessError(_('Only sales managers can approve orders')) self.approval_status = 'approved' self._send_approval_confirmation() # Auto-confirm if conditions are met if self.state == 'draft': self.action_confirm() return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'message': _('Order approved successfully'), 'type': 'success', } } def action_reject(self): """Reject the order""" self.ensure_one() if not self.env.user.has_group('sales_team.group_sale_manager'): raise AccessError(_('Only sales managers can reject orders')) self.approval_status = 'rejected' return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'message': _('Order rejected'), 'type': 'warning', } } ``` ### 4. Integration Enhancement #### External System Integration ```python class SaleOrderIntegration(models.Model): _inherit = 'sale.order' external_system_id = fields.Char( string='External System ID', help='ID in external system for synchronization' ) last_sync_date = fields.Datetime( string='Last Sync Date', readonly=True ) sync_status = fields.Selection([ ('pending', 'Pending Sync'), ('synced', 'Synced'), ('error', 'Sync Error') ], string='Sync Status', default='pending') def action_sync_external_system(self): """Synchronize with external system""" external_api = self.env['external.api.connector'] for order in self: try: # Prepare data for external system data = { 'order_number': order.name, 'customer_id': order.partner_id.external_id, 'total_amount': order.amount_total, 'order_lines': [{ 'product_code': line.product_id.default_code, 'quantity': line.product_uom_qty, 'price': line.price_unit, } for line in order.order_line] } # Send to external system response = external_api.create_order(data) # Update order with external ID order.write({ 'external_system_id': response.get('id'), 'sync_status': 'synced', 'last_sync_date': fields.Datetime.now() }) except Exception as e: order.write({ 'sync_status': 'error', 'last_sync_date': fields.Datetime.now() }) _logger.error(f'Sync error for order {order.name}: {e}') @api.model def cron_sync_orders(self): """Scheduled job to sync pending orders""" pending_orders = self.search([ ('sync_status', '=', 'pending'), ('state', 'in', ['sale', 'done']) ]) pending_orders.action_sync_external_system() ``` ### 5. Reporting Enhancement #### Custom Reports ```python class SaleReportExtension(models.Model): _inherit = 'sale.report' approval_required = fields.Boolean( string='Approval Required', readonly=True ) approval_status = fields.Selection([ ('pending', 'Pending Approval'), ('approved', 'Approved'), ('rejected', 'Rejected') ], string='Approval Status', readonly=True) def _select_additional_fields(self): res = super()._select_additional_fields() res['approval_required'] = "s.approval_required" res['approval_status'] = "s.approval_status" return res def _group_by_sale(self): res = super()._group_by_sale() res += ", s.approval_required, s.approval_status" return res ``` ### 6. Security Enhancement #### Enhanced Security Rules ```xml <?xml version="1.0" encoding="utf-8"?> <odoo> <!-- Enhanced record rules --> <record id="sale_order_approval_rule" model="ir.rule"> <field name="name">Sale Order: Approval Access</field> <field name="model_id" ref="sale.model_sale_order"/> <field name="domain_force">[ '|', ('approval_required', '=', False), '|', ('user_id', '=', user.id), ('approval_status', '=', 'approved') ]</field> <field name="groups" eval="[(4, ref('sales_team.group_sale_salesman'))]"/> </record> <!-- Manager override rule --> <record id="sale_order_manager_rule" model="ir.rule"> <field name="name">Sale Order: Manager Full Access</field> <field name="model_id" ref="sale.model_sale_order"/> <field name="domain_force">[(1, '=', 1)]</field> <field name="groups" eval="[(4, ref('sales_team.group_sale_manager'))]"/> </record> </odoo> ``` ## Implementation Best Practices ### 1. Inheritance Strategy - **Prefer Extension**: Use `_inherit` to extend existing models - **Minimize Core Changes**: Avoid modifying core Odoo code directly - **Maintain Compatibility**: Ensure changes don't break existing functionality - **Document Dependencies**: Clearly document module dependencies ### 2. Database Safety ```python def safe_field_addition(self): """Safe method to add fields without data loss""" # Check if field exists before adding if not hasattr(self, 'new_field'): self._add_field('new_field', fields.Char('New Field')) # Migrate existing data if needed if not self.search([('new_field', '!=', False)]): self._migrate_existing_data() def _migrate_existing_data(self): """Migrate existing data to new fields""" orders = self.search([('new_field', '=', False)]) for order in orders: # Logic to populate new field based on existing data order.new_field = self._calculate_new_field_value(order) ``` ### 3. Testing Strategy ```python class TestSaleOrderEnhancement(TransactionCase): def setUp(self): super().setUp() self.partner = self.env.ref('base.res_partner_1') self.product = self.env.ref('product.product_product_3') def test_approval_workflow(self): """Test approval workflow for high-value orders""" # Create high-value order order = self.env['sale.order'].create({ 'partner_id': self.partner.id, 'order_line': [(0, 0, { 'product_id': self.product.id, 'product_uom_qty': 100, 'price_unit': 1000, })] }) # Verify approval is required self.assertTrue(order.approval_required) self.assertEqual(order.approval_status, 'pending') # Test approval workflow order.action_approve() self.assertEqual(order.approval_status, 'approved') def test_backward_compatibility(self): """Test that existing functionality still works""" # Create standard order order = self.env['sale.order'].create({ 'partner_id': self.partner.id, 'order_line': [(0, 0, { 'product_id': self.product.id, 'product_uom_qty': 1, 'price_unit': 100, })] }) # Verify standard workflow works order.action_confirm() self.assertEqual(order.state, 'sale') def test_data_integrity(self): """Test that enhancements don't break data integrity""" # Test field constraints with self.assertRaises(ValidationError): self.env['sale.order'].create({ 'partner_id': self.partner.id, 'custom_reference': 'X' * 1000, # Too long }) ``` ### 4. Migration Considerations ```python def migrate_existing_orders(cr, version): """Migration script for existing installations""" if not version: return # Add new columns safely cr.execute(""" ALTER TABLE sale_order ADD COLUMN IF NOT EXISTS custom_reference VARCHAR(255), ADD COLUMN IF NOT EXISTS approval_required BOOLEAN DEFAULT FALSE, ADD COLUMN IF NOT EXISTS approval_status VARCHAR(20) DEFAULT 'pending' """) # Update existing records cr.execute(""" UPDATE sale_order SET approval_required = TRUE WHERE amount_total > 1000 """) # Create necessary indexes cr.execute(""" CREATE INDEX IF NOT EXISTS sale_order_approval_status_idx ON sale_order(approval_status) """) ``` ## Quality Assurance ### Pre-deployment Checklist - [ ] All existing functionality tested and working - [ ] New features implemented according to specifications - [ ] Database migration scripts tested - [ ] Security rules properly configured - [ ] Performance impact assessed - [ ] Documentation updated - [ ] User training materials prepared - [ ] Rollback procedures documented ### Performance Monitoring ```python def monitor_performance_impact(self): """Monitor performance impact of enhancements""" import time start_time = time.time() # Test critical operations orders = self.env['sale.order'].search([]) for order in orders[:100]: order.action_confirm() end_time = time.time() duration = end_time - start_time if duration > 60: # More than 1 minute for 100 orders _logger.warning(f'Performance degradation detected: {duration}s') ``` ## Deployment Strategy ### Staged Rollout 1. **Development Environment**: Complete testing and validation 2. **Staging Environment**: User acceptance testing 3. **Production Deployment**: Phased rollout with monitoring 4. **Post-deployment**: Performance monitoring and user feedback ### Rollback Plan - Database backup before deployment - Code rollback procedures - Data restoration processes - User communication plan Remember: Enhancement success depends on thorough testing, careful planning, and maintaining backward compatibility while adding valuable new functionality.