Odoo18-Base/addons/sale/controllers/combo_configurator.py
2025-01-06 10:57:38 +07:00

210 lines
8.8 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from odoo.http import Controller, request, route
from odoo.tools import groupby
class SaleComboConfiguratorController(Controller):
@route(route='/sale/combo_configurator/get_data', type='json', auth='user')
def sale_combo_configurator_get_data(
self,
product_tmpl_id,
quantity,
date,
currency_id=None,
company_id=None,
pricelist_id=None,
selected_combo_items=None,
**kwargs,
):
""" Return data about the specified combo product.
:param int product_tmpl_id: The product for which to get data, as a `product.template` id.
:param int quantity: The quantity of the product.
:param str date: The date to use to compute prices.
:param int|None currency_id: The currency to use to compute prices, as a `res.currency` id.
:param int|None company_id: The company to use, as a `res.company` id.
:param int|None pricelist_id: The pricelist to use to compute prices, as a
`product.pricelist` id.
:param list(dict) selected_combo_items: The selected combo items, in the following format:
{
'id': int,
'no_variant_ptav_ids': list(int),
'custom_ptavs': list({
'id': int,
'value': str,
}),
}
:param dict kwargs: Locally unused data passed to `_get_configurator_display_price` and
`_get_additional_configurator_data`.
:rtype: dict
:return: A dict containing data about the combo product.
"""
if company_id:
request.update_context(allowed_company_ids=[company_id])
product_template = request.env['product.template'].browse(product_tmpl_id)
currency = request.env['res.currency'].browse(currency_id)
pricelist = request.env['product.pricelist'].browse(pricelist_id)
date = datetime.fromisoformat(date)
selected_combo_item_dict = {item['id']: item for item in selected_combo_items or []}
return {
'product_tmpl_id': product_tmpl_id,
'display_name': product_template.display_name,
'quantity': quantity,
'price': product_template._get_configurator_display_price(
product_template, quantity, date, currency, pricelist, **kwargs
)[0],
'combos': [{
'id': combo.id,
'name': combo.name,
'combo_items': [
self. _get_combo_item_data(
combo,
combo_item,
selected_combo_item_dict.get(combo_item.id, {}),
date,
currency,
pricelist,
**kwargs,
) for combo_item in combo.combo_item_ids
],
} for combo in product_template.combo_ids.sudo()],
'currency_id': currency_id,
**product_template._get_additional_configurator_data(
product_template, date, currency, pricelist, **kwargs
),
}
@route(route='/sale/combo_configurator/get_price', type='json', auth='user')
def sale_combo_configurator_get_price(
self,
product_tmpl_id,
quantity,
date,
currency_id=None,
company_id=None,
pricelist_id=None,
**kwargs,
):
""" Return the price of the specified combo product.
:param int product_tmpl_id: The product for which to get the price, as a `product.template`
id.
:param int quantity: The quantity of the product.
:param str date: The date to use to compute the price.
:param int|None currency_id: The currency to use to compute the price, as a `res.currency`
id.
:param int|None company_id: The company to use, as a `res.company` id.
:param int|None pricelist_id: The pricelist to use to compute the price, as a
`product.pricelist` id.
:param dict kwargs: Locally unused data passed to `_get_configurator_display_price`.
:rtype: float
:return: The price of the combo product.
"""
if company_id:
request.update_context(allowed_company_ids=[company_id])
product_template = request.env['product.template'].browse(product_tmpl_id)
currency = request.env['res.currency'].browse(currency_id)
pricelist = request.env['product.pricelist'].browse(pricelist_id)
date = datetime.fromisoformat(date)
return product_template._get_configurator_display_price(
product_template, quantity, date, currency, pricelist, **kwargs
)[0]
def _get_combo_item_data(
self, combo, combo_item, selected_combo_item, date, currency, pricelist, **kwargs
):
""" Return the price of the specified combo product.
:param product.combo combo: The combo for which to get the data.
:param product.combo.item combo_item: The combo for which to get the data.
:param datetime date: The date to use to compute prices.
:param product.pricelist pricelist: The pricelist to use to compute prices.
:param dict kwargs: Locally unused data passed to `_get_additional_configurator_data`.
:rtype: dict
:return: A dict containing data about the combo item.
"""
ptals_data = self._get_ptals_data(combo_item.product_id, selected_combo_item)
# If the combo choice has only one combo item, and that combo item can't be configured (i.e.
# it has no configurable `no_variant` attributes), then it should be preselected, as the
# user has to select it anyway.
is_preselected = (
len(combo.combo_item_ids) == 1
and not any(
ptal.attribute_id.create_variant == 'no_variant' and ptal._is_configurable()
for ptal in combo_item.product_id.attribute_line_ids
)
)
return {
'id': combo_item.id,
'extra_price': combo_item.extra_price,
'is_selected': bool(selected_combo_item),
'is_preselected': is_preselected,
'product': {
'id': combo_item.product_id.id,
'product_tmpl_id': combo_item.product_id.product_tmpl_id.id,
'display_name': combo_item.product_id.display_name,
'ptals': ptals_data,
**request.env['product.template']._get_additional_configurator_data(
combo_item.product_id, date, currency, pricelist, **kwargs
),
},
}
def _get_ptals_data(self, product, selected_combo_item):
""" Return data about the PTALs of the specified product.
:param product.product product: The product for which to get the PTALs.
:param dict selected_combo_item: The selected combo item, in the following format:
{
'id': int,
'no_variant_ptav_ids': list(int),
'custom_ptavs': list({
'id': int,
'value': str,
}),
}
:rtype: list(dict)
:return: A list of dicts containing data about the specified product's PTALs.
"""
variant_ptavs = product.product_template_attribute_value_ids
no_variant_ptavs = request.env['product.template.attribute.value'].browse(
selected_combo_item.get('no_variant_ptav_ids')
)
ptavs_by_ptal_id = dict(groupby(
variant_ptavs + no_variant_ptavs, lambda ptav: ptav.attribute_line_id.id
))
custom_ptavs = selected_combo_item.get('custom_ptavs', [])
custom_value_by_ptav_id = {ptav['id']: ptav['value'] for ptav in custom_ptavs}
return [{
'id': ptal.id,
'name': ptal.attribute_id.name,
'create_variant': ptal.attribute_id.create_variant,
'selected_ptavs': self._get_selected_ptavs_data(
ptavs_by_ptal_id.get(ptal.id, []), custom_value_by_ptav_id
),
} for ptal in product.attribute_line_ids]
def _get_selected_ptavs_data(self, selected_ptavs, custom_value_by_ptav_id):
""" Return data about the selected PTAVs of the specified product.
:param list(product.template.attribute.value) selected_ptavs: The selected PTAVs.
:param dict custom_value_by_ptav_id: A mapping from PTAV ids to custom values.
:rtype: list(dict)
:return: A list of dicts containing data about the specified PTAL's selected PTAVs.
"""
return [{
'id': ptav.id,
'name': ptav.name,
'price_extra': ptav.price_extra,
'custom_value': custom_value_by_ptav_id.get(ptav.id),
} for ptav in selected_ptavs]