19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:12 +01:00
parent 79f83631d5
commit 73afc09215
6267 changed files with 1534193 additions and 1130106 deletions

View file

@ -1,29 +1,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models, fields, _
from odoo import api, fields, models
from odoo.exceptions import UserError
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
_inherit = 'sale.order.line'
linked_line_id = fields.Many2one('sale.order.line', string='Linked Order Line', domain="[('order_id', '=', order_id)]", ondelete='cascade', copy=False, index=True)
option_line_ids = fields.One2many('sale.order.line', 'linked_line_id', string='Options Linked')
name_short = fields.Char(compute="_compute_name_short")
shop_warning = fields.Char('Warning')
name_short = fields.Char(compute='_compute_name_short')
shop_warning = fields.Char(string="Warning")
#=== COMPUTE METHODS ===#
@api.depends('linked_line_id', 'option_line_ids')
def _compute_name(self):
"""Override to add the compute dependency.
The custom name logic can be found below in _get_sale_order_line_multiline_description_sale.
"""
super()._compute_name()
@api.depends('product_id.display_name')
def _compute_name_short(self):
""" Compute a short name for this sale order line, to be used on the website where we don't have much space.
@ -34,19 +22,17 @@ class SaleOrderLine(models.Model):
#=== BUSINESS METHODS ===#
def _get_sale_order_line_multiline_description_sale(self):
description = super()._get_sale_order_line_multiline_description_sale()
if self.linked_line_id:
description += "\n" + _("Option for: %s", self.linked_line_id.product_id.display_name)
if self.option_line_ids:
description += "\n" + '\n'.join([
_("Option: %s", option_line.product_id.display_name)
for option_line in self.option_line_ids
])
return description
def get_description_following_lines(self):
return self.name.splitlines()[1:]
return reversed(self.name.splitlines()[1:])
def _get_combination_name(self):
return self.product_id.product_template_attribute_value_ids._get_combination_name()
def _get_line_header(self):
if not self.product_template_attribute_value_ids:
return self.name_short
# not display_name because we don't want the combination name or the code.
return self.product_id.name
def _get_order_date(self):
self.ensure_one()
@ -63,10 +49,84 @@ class SaleOrderLine(models.Model):
self.shop_warning = ''
return warn
def _get_displayed_unit_price(self):
show_tax = self.order_id.website_id.show_line_subtotals_tax_selection
tax_display = 'total_excluded' if show_tax == 'tax_excluded' else 'total_included'
is_combo = self.product_type == 'combo'
unit_price = self._get_display_price_ignore_combo() if is_combo else self.price_unit
return self.tax_ids.compute_all(
unit_price, self.currency_id, 1, self.product_id, self.order_partner_id,
)[tax_display]
def _get_selected_combo_items(self):
if self.product_id.type == 'combo':
return [{
'id': linked_line.combo_item_id.id,
'no_variant_ptav_ids': linked_line.product_no_variant_attribute_value_ids.ids,
'custom_ptavs': [{
'id': pcav.custom_product_template_attribute_value_id.id,
'value': pcav.custom_value,
} for pcav in linked_line.product_custom_attribute_value_ids]
} for linked_line in self.linked_line_ids]
return None
def _get_displayed_quantity(self):
rounded_uom_qty = round(self.product_uom_qty,
self.env['decimal.precision'].precision_get('Product Unit'))
return int(rounded_uom_qty) == rounded_uom_qty and int(rounded_uom_qty) or rounded_uom_qty
def _show_in_cart(self):
self.ensure_one()
return not bool(self.display_type)
# Exclude delivery & section/note lines from showing up in the cart
return not self.is_delivery and not bool(self.display_type) and not bool(self.combo_item_id)
def _is_reorder_allowed(self):
self.ensure_one()
return self.product_id._is_add_to_cart_allowed()
return (
bool(self.product_id)
and self.product_id._is_add_to_cart_allowed()
and self._show_in_cart()
)
def _get_cart_display_price(self):
self.ensure_one()
price_type = (
'price_subtotal'
if self.order_id.website_id.show_line_subtotals_tax_selection == 'tax_excluded'
else 'price_total'
)
return sum(self._get_lines_with_price().mapped(price_type))
def _check_validity(self):
if (
not self.combo_item_id
and sum(self._get_lines_with_price().mapped('price_unit')) == 0
and self.order_id.website_id.prevent_zero_price_sale
and self.product_template_id.service_tracking not in self.env['product.template']._get_product_types_allow_zero_price()
):
raise UserError(self.env._(
"The given product does not have a price therefore it cannot be added to cart.",
))
def _should_show_strikethrough_price(self):
""" Compute whether the strikethrough price should be shown.
The strikethrough price should be shown if there is a discount on a sellable line for
which a price unit is non-zero.
:return: Whether the strikethrough price should be shown.
:rtype: bool
"""
return self.discount and self._is_sellable() and self._get_displayed_unit_price()
def _is_sellable(self):
"""Check if a line is sellable or not, i.e the link is clickable in the cart or not.
A line is sellable if the product is published and not a delivery line.
:return: Whether the line is sellable or not.
:rtype: bool
"""
return self.product_id.is_published and not self.is_delivery