mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 19:52:06 +02:00
233 lines
8.7 KiB
Python
233 lines
8.7 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import ValidationError
|
|
from odoo.http import request
|
|
from odoo.tools import float_round
|
|
|
|
|
|
class ProductProduct(models.Model):
|
|
_inherit = 'product.product'
|
|
_mail_post_access = 'read'
|
|
|
|
variant_ribbon_id = fields.Many2one(string="Variant Ribbon", comodel_name='product.ribbon')
|
|
website_id = fields.Many2one(related='product_tmpl_id.website_id', readonly=False)
|
|
|
|
product_variant_image_ids = fields.One2many(
|
|
string="Extra Variant Images",
|
|
comodel_name='product.image',
|
|
inverse_name='product_variant_id',
|
|
)
|
|
|
|
base_unit_count = fields.Float(
|
|
string="Base Unit Count",
|
|
help="Display base unit price on your eCommerce pages. Set to 0 to hide it for this"
|
|
" product.",
|
|
required=True,
|
|
default=1,
|
|
)
|
|
base_unit_id = fields.Many2one(
|
|
string="Custom Unit of Measure",
|
|
help="Define a custom unit to display in the price per unit of measure field.",
|
|
comodel_name='website.base.unit',
|
|
)
|
|
base_unit_price = fields.Monetary(
|
|
string="Price Per Unit",
|
|
compute='_compute_base_unit_price',
|
|
)
|
|
base_unit_name = fields.Char(
|
|
help="Displays the custom unit for the products if defined or the selected unit of measure"
|
|
" otherwise.",
|
|
compute='_compute_base_unit_name',
|
|
)
|
|
|
|
website_url = fields.Char(
|
|
string="Website URL",
|
|
help="The full URL to access the document through the website.",
|
|
compute='_compute_product_website_url',
|
|
)
|
|
|
|
#=== COMPUTE METHODS ===#
|
|
|
|
def _get_base_unit_price(self, price):
|
|
self.ensure_one()
|
|
return self.base_unit_count and price / self.base_unit_count
|
|
|
|
@api.depends('lst_price', 'base_unit_count')
|
|
def _compute_base_unit_price(self):
|
|
for product in self:
|
|
if not product.id:
|
|
product.base_unit_price = 0
|
|
else:
|
|
product.base_unit_price = product._get_base_unit_price(product.lst_price)
|
|
|
|
@api.depends('uom_name', 'base_unit_id')
|
|
def _compute_base_unit_name(self):
|
|
for product in self:
|
|
product.base_unit_name = product.base_unit_id.name or product.uom_name
|
|
|
|
@api.depends_context('lang')
|
|
@api.depends('product_tmpl_id.website_url', 'product_template_attribute_value_ids')
|
|
def _compute_product_website_url(self):
|
|
for product in self:
|
|
url = product.product_tmpl_id.website_url
|
|
if pavs := product.product_template_attribute_value_ids.product_attribute_value_id:
|
|
pav_ids = [str(pav.id) for pav in pavs]
|
|
url = f'{url}?attribute_values={",".join(pav_ids)}'
|
|
product.website_url = url
|
|
|
|
#=== CONSTRAINT METHODS ===#
|
|
|
|
@api.constrains('base_unit_count')
|
|
def _check_base_unit_count(self):
|
|
if any(product.base_unit_count < 0 for product in self):
|
|
raise ValidationError(_(
|
|
"The value of Base Unit Count must be greater than 0."
|
|
" Use 0 to hide the price per unit on this product."
|
|
))
|
|
|
|
#=== BUSINESS METHODS ===#
|
|
|
|
def _prepare_variant_values(self, combination):
|
|
variant_dict = super()._prepare_variant_values(combination)
|
|
variant_dict['base_unit_count'] = self.base_unit_count
|
|
return variant_dict
|
|
|
|
def website_publish_button(self):
|
|
self.ensure_one()
|
|
return self.product_tmpl_id.website_publish_button()
|
|
|
|
def open_website_url(self):
|
|
self.ensure_one()
|
|
res = self.product_tmpl_id.open_website_url()
|
|
res['url'] = self.website_url
|
|
return res
|
|
|
|
def _get_images(self):
|
|
"""Return a list of records implementing `image.mixin` to
|
|
display on the carousel on the website for this variant.
|
|
|
|
This returns a list and not a recordset because the records might be
|
|
from different models (template, variant and image).
|
|
|
|
It contains in this order: the main image of the variant (which will fall back on the main
|
|
image of the template, if unset), the Variant Extra Images, and the Template Extra Images.
|
|
"""
|
|
self.ensure_one()
|
|
variant_images = list(self.product_variant_image_ids)
|
|
template_images = list(self.product_tmpl_id.product_template_image_ids)
|
|
return [self] + variant_images + template_images
|
|
|
|
def _get_combination_info_variant(self, **kwargs):
|
|
"""Return the variant info based on its combination.
|
|
See `_get_combination_info` for more information.
|
|
"""
|
|
self.ensure_one()
|
|
return self.product_tmpl_id._get_combination_info(
|
|
combination=self.product_template_attribute_value_ids,
|
|
product_id=self.id,
|
|
**kwargs)
|
|
|
|
def _website_show_quick_add(self):
|
|
self.ensure_one()
|
|
if not self.filtered_domain(self.env['website']._product_domain()):
|
|
return False
|
|
return not request.website.prevent_zero_price_sale or self._get_contextual_price()
|
|
|
|
def _is_add_to_cart_allowed(self):
|
|
self.ensure_one()
|
|
if self.env.user.has_group('base.group_system'):
|
|
return True
|
|
if not self.active or not self.website_published:
|
|
return False
|
|
if not self.filtered_domain(self.env['website']._product_domain()):
|
|
return False
|
|
if request.website.prevent_zero_price_sale and not self._get_contextual_price():
|
|
return False
|
|
return request.website.has_ecommerce_access()
|
|
|
|
@api.onchange('public_categ_ids')
|
|
def _onchange_public_categ_ids(self):
|
|
if self.public_categ_ids:
|
|
self.website_published = True
|
|
else:
|
|
self.website_published = False
|
|
|
|
def _to_markup_data(self, website):
|
|
""" Generate JSON-LD markup data for the current product.
|
|
|
|
:param website website: The current website.
|
|
:return: The JSON-LD markup data.
|
|
:rtype: dict
|
|
"""
|
|
self.ensure_one()
|
|
|
|
product_price = request.pricelist._get_product_price(
|
|
self, quantity=1, currency=website.currency_id
|
|
)
|
|
# Use sudo to access cross-company taxes.
|
|
product_taxes_sudo = self.sudo().taxes_id._filter_taxes_by_company(self.env.company)
|
|
taxes = request.fiscal_position.map_tax(product_taxes_sudo)
|
|
price = self.product_tmpl_id._apply_taxes_to_price(
|
|
product_price, website.currency_id, product_taxes_sudo, taxes, self, website=website
|
|
)
|
|
|
|
base_url = website.get_base_url()
|
|
markup_data = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'Product',
|
|
'name': self.with_context(display_default_code=False).display_name,
|
|
'url': f'{base_url}{self.website_url}',
|
|
'image': f'{base_url}{website.image_url(self, "image_1920")}',
|
|
'offers': {
|
|
'@type': 'Offer',
|
|
'price': price,
|
|
'priceCurrency': website.currency_id.name,
|
|
},
|
|
}
|
|
if self.website_meta_description or self.description_sale:
|
|
markup_data['description'] = self.website_meta_description or self.description_sale
|
|
if website.is_view_active('website_sale.product_comment') and self.rating_count:
|
|
markup_data['aggregateRating'] = {
|
|
'@type': 'AggregateRating',
|
|
# sudo: product.product - visitor can access product average rating
|
|
'ratingValue': self.sudo().rating_avg,
|
|
'reviewCount': self.rating_count,
|
|
}
|
|
return markup_data
|
|
|
|
def _get_image_1920_url(self):
|
|
""" Returns the local url of the product main image.
|
|
|
|
Note: self.ensure_one()
|
|
|
|
:rtype: str
|
|
"""
|
|
self.ensure_one()
|
|
return self.env['website'].image_url(self, 'image_1920')
|
|
|
|
def _get_extra_image_1920_urls(self):
|
|
""" Returns the local url of the product additional images, no videos. This includes the
|
|
variant specific images first and then the template images.
|
|
|
|
Note: self.ensure_one()
|
|
|
|
:rtype: list[str]
|
|
"""
|
|
self.ensure_one()
|
|
return [
|
|
self.env['website'].image_url(extra_image, 'image_1920')
|
|
for extra_image in self.product_variant_image_ids + self.product_template_image_ids
|
|
if extra_image.image_128 # only images, no video urls
|
|
]
|
|
|
|
def write(self, vals):
|
|
if 'active' in vals and not vals['active']:
|
|
# unlink draft lines containing the archived product
|
|
self.env['sale.order.line'].sudo().search([
|
|
('state', '=', 'draft'),
|
|
('product_id', 'in', self.ids),
|
|
('order_id', 'any', [('website_id', '!=', False)]),
|
|
]).unlink()
|
|
return super().write(vals)
|