[IMP] sale_management: no client reload when adding optional product
Purpose ======= Avoid a complete reload of the web client when adding a optional product, which loses searches, breadcrumb, etc. This is achieved by adding a non-stored 'is_present' computed field which is computed depending on the presence of a SO line linked to the option line - that way its is dynamically adapted when the option is added to the SO. Since we wanted a clear feedback when the option was added to the quote, green colouring and the disappearance of the 'add to quote' button have been added. Note that this field *cannot* be stored - since the line_id field on the option bypasses the ORM (`set null` for the on_delete attribute), it would not recompute properly upon deletion of the linked SO line. Task#1950410 closes odoo/odoo#34929 Signed-off-by: Damien Bouvy (dbo) <dbo@odoo.com>
This commit is contained in:
@@ -153,8 +153,12 @@ class SaleOrderOption(models.Model):
|
||||
_description = "Sale Options"
|
||||
_order = 'sequence, id'
|
||||
|
||||
is_present = fields.Boolean(string="Present on Quotation",
|
||||
help="This field will be checked if the option line's product is "
|
||||
"already present in the quotation.",
|
||||
compute="_compute_is_present", search="_search_is_present")
|
||||
order_id = fields.Many2one('sale.order', 'Sales Order Reference', ondelete='cascade', index=True)
|
||||
line_id = fields.Many2one('sale.order.line', ondelete="set null")
|
||||
line_id = fields.Many2one('sale.order.line', ondelete="set null", copy=False)
|
||||
name = fields.Text('Description', required=True)
|
||||
product_id = fields.Many2one('product.product', 'Product', required=True, domain=[('sale_ok', '=', True)])
|
||||
price_unit = fields.Float('Unit Price', required=True, digits='Product Price')
|
||||
@@ -163,6 +167,18 @@ class SaleOrderOption(models.Model):
|
||||
quantity = fields.Float('Quantity', required=True, digits='Product UoS', default=1)
|
||||
sequence = fields.Integer('Sequence', help="Gives the sequence order when displaying a list of optional products.")
|
||||
|
||||
@api.depends('line_id', 'order_id.order_line', 'product_id')
|
||||
def _compute_is_present(self):
|
||||
# NOTE: this field cannot be stored as the line_id is usually removed
|
||||
# through cascade deletion, which means the compute would be false
|
||||
for option in self:
|
||||
option.is_present = bool(option.order_id.order_line.filtered(lambda l: l.product_id == option.product_id))
|
||||
|
||||
def _search_is_present(self, operator, value):
|
||||
if (operator, value) in [('=', True), ('!=', False)]:
|
||||
return [('line_id', '=', False)]
|
||||
return [('line_id', '!=', False)]
|
||||
|
||||
@api.onchange('product_id', 'uom_id')
|
||||
def _onchange_product_id(self):
|
||||
if not self.product_id:
|
||||
@@ -181,7 +197,6 @@ class SaleOrderOption(models.Model):
|
||||
@api.multi
|
||||
def button_add_to_order(self):
|
||||
self.add_option_to_order()
|
||||
return {'type': 'ir.actions.client', 'tag': 'reload'}
|
||||
|
||||
@api.multi
|
||||
def add_option_to_order(self):
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<field name="uom_id" groups="uom.group_uom"/>
|
||||
<field name="price_unit"/>
|
||||
<field name="discount" groups="sale.group_discount_per_so_line"/>
|
||||
<field name="is_present" />
|
||||
</group>
|
||||
</form>
|
||||
<kanban class="o_kanban_mobile">
|
||||
@@ -24,6 +25,7 @@
|
||||
<field name="quantity"/>
|
||||
<field name="uom_id" groups="uom.group_uom"/>
|
||||
<field name="price_unit"/>
|
||||
<field name="is_present" />
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_card oe_kanban_global_click">
|
||||
@@ -36,7 +38,7 @@
|
||||
</strong>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<button name="button_add_to_order" class="btn btn-link oe_link fa fa-shopping-cart" string="Add to order lines" type="object"/>
|
||||
<button name="button_add_to_order" class="btn btn-link oe_link fa fa-shopping-cart" title="Add to order lines" type="object" attrs="{'invisible': [('is_present', '=', True)]}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -60,7 +62,7 @@
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
<tree string="Sales Quotation Template Lines" editable="bottom">
|
||||
<tree string="Sales Quotation Template Lines" editable="bottom" decoration-success="is_present == True">
|
||||
<control>
|
||||
<create name="add_product_control" string="Add a product"/>
|
||||
</control>
|
||||
@@ -71,7 +73,8 @@
|
||||
<field name="uom_id" string="UoM" groups="uom.group_uom" optional="show"/>
|
||||
<field name="price_unit"/>
|
||||
<field name="discount" string="Disc.%" groups="sale.group_discount_per_so_line" optional="show"/>
|
||||
<button name="button_add_to_order" class="oe_link" icon="fa-shopping-cart" string="Add to order lines" type="object"/>
|
||||
<field name="is_present" invisible="1" />
|
||||
<button name="button_add_to_order" class="oe_link" icon="fa-shopping-cart" title="Add to order lines" type="object" attrs="{'invisible': [('is_present', '=', True)]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//section[@id='signature']" position="after">
|
||||
<t t-if="any([(not option.line_id) for option in sale_order.sale_order_option_ids])">
|
||||
<t t-if="any([(not option.is_present) for option in sale_order.sale_order_option_ids])">
|
||||
<section>
|
||||
<h3>Options</h3>
|
||||
<t t-set="display_discount" t-value="True in [option.discount > 0 for option in sale_order.sale_order_option_ids]"/>
|
||||
@@ -44,7 +44,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="sale_order.sale_order_option_ids" t-as="option">
|
||||
<t t-if="not option.line_id">
|
||||
<t t-if="not option.is_present">
|
||||
<td>
|
||||
<div t-field="option.name"/>
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user