Odoo Partial Approval Offer Management Module — Part 2
In many companies, the sales workflow does not end with a simple “Approved” or “Rejected” quotation. 🚀
A customer may approve only specific quotation lines while cancelling the remaining items.
For example:
- A quotation contains 5 products
- The customer approves only 2 products
- The remaining 3 products are cancelled
In standard workflows, sales teams usually create a completely new quotation template to manage this situation. However, this approach causes an important CRM problem:
- Original quotation history becomes fragmented
- Approval timeline is lost
- Product performance analysis becomes inaccurate
- Sales cycle tracking becomes harder
- Customer behavior data becomes inconsistent
Solution
This module introduces a Partial Approval System for Odoo quotations. If you want to use this module please contact with me.
Features
- Approve selected quotation lines
- Cancel remaining lines with cancellation reasons
- Keep all actions inside the original quotation
- Track approval and cancellation history
- Preserve CRM sales timeline
- Analyze customer decision patterns
- Measure product acceptance performance
- Improve pipeline analytics
Example Workflow
- Sales team sends quotation to customer
- Customer partially approves the offer
- Salesperson selects approved lines
- Remaining lines are marked as cancelled
- Cancellation reasons are recorded
- Original quotation remains fully traceable
This helps companies better understand:
- Which products are frequently rejected
- Average approval duration
- Customer purchasing behavior
- Salesperson performance
- Pipeline conversion quality
CRM should track the complete sales journey from start to finish.
This module helps preserve that journey without losing quotation history.
Get Damla Yaşar’s stories in your inbox
Join Medium for free to get updates from this writer.
In the following days, I will also share pipeline analytics and reporting ideas built on top of this workflow in Odoo.
Odoo Code Architecture & Extension Logic 🍀
Odoo has a very stable and modular code structure.
This architecture allows developers to easily extend existing features or build completely new modules without breaking the core system.
1. Module Structure is Strict
Every Odoo module follows a standard structure:
The __init__.py file is especially important because it defines which Python files will be loaded by Odoo.
2. Module Naming & Structure Matters
Odoo relies heavily on naming conventions:
- Folder name = module technical name
- Python class
_name= database model name - XML IDs = referenced system-wide
So consistency between:
- file structure
- model names
- XML records
is critical for the system to work properly.
3. Everything Works with Inheritance
One of the most powerful concepts in Odoo is inheritance-based extension.
Instead of modifying core code, you extend it.
from odoo import models, fields, api
from odoo.exceptions import ValidationError
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'🔑 You are NOT rewriting the module, You are extending existing functionality
Manifest.xml
this is the anothoer important script for odoo, if you are not create this script, you module will not work smoothly.
{
'name': 'Sale Line Partial Approval',
'version': '1.0',
'author': 'damlaYasarr',
'category': 'Sales',
'depends': ['sale'],
'data': [
'views/sale_order_views.xml',
],
'installable': True,
'application': False,
}Each folder you have to create _init_.py script and then you show your from . import sale_order_line
from odoo import models, fields, api
from odoo.exceptions import ValidationError
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
line_approval_state = fields.Selection([
('pending', 'Bekliyor'),
('approved', 'Onaylandı'),
('rejected', 'Reddedildi'),
], default='pending', string="Satır Onay Durumu")
loss_reason_id = fields.Many2one(
'crm.lost.reason',
string="Kayıp Nedeni",
domain=lambda self: self._loss_reason_domain(),
)Purpose of Views
Views define:
- How records are displayed in UI
- Which fields are visible
- Layout of forms, lists, kanban, etc.
- User interaction structure
In short:
Python = logic
XML = UI representation
<odoo>
<record id="view_order_form_partial_approval" model="ir.ui.view">
<field name="name">sale.order.form.partial.approval</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='order_line']//list" position="inside">
<field name="line_approval_state"/>
<field name="loss_reason_id"
attrs="{'invisible':[('line_approval_state','!=','rejected')]}"/>
</xpath>
</field>
</record>
</odoo>
Views in Odoo are defined using XML files.
They control how the data from Python models is displayed in the user interface.
The fields defined in Python models are referenced inside XML views to render forms, lists, and other UI components.
Thank you for reading!








