Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions website_product_configurator_restriction/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#Website Product Configurator Restriction

This module facilitates to restriction product on website.
3 changes: 3 additions & 0 deletions website_product_configurator_restriction/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import models
from . import controllers
# from . import tests
32 changes: 32 additions & 0 deletions website_product_configurator_restriction/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "Website Product Configurator Restriction",
"version": "18.0.1.0.0",
"summary": """Website configure products restriction in e-shop""",
"author": "Pledra, Odoo Community Association (OCA)",
"license": "AGPL-3",
"website": "https://github.com/OCA/product-configurator",
"category": "website",
"depends": [
"website_sale",
"product_configurator",
"product_configurator_sale",
],
"data": [
"security/configurator_security.xml",
"data/data_file.xml",
],
"images": ["static/description/cover.png"],
"application": True,
"installable": True,
"development_status": "Beta",
"maintainers": ["PCatinean"],
"assets": {
"web.assets_frontend": [
"website_product_configurator_restriction/static/src/js/variant_mixin.js",
"website_product_configurator_restriction/static/src/js/website_sale.js",
],
# "web.assets_tests": [
# "website_product_configurator_restriction/static/tests/tours/**/*"
# ],
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import main
231 changes: 231 additions & 0 deletions website_product_configurator_restriction/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
from odoo import http
from odoo.http import request
from odoo.tools.safe_eval import safe_eval

from odoo.addons.website_sale.controllers.main import WebsiteSale


class ProductConfigWebsiteRestriction(WebsiteSale):

def get_config_session(self, product_tmpl_id):
cfg_session_obj = request.env["product.config.session"]
cfg_session = False
product_config_sessions = request.session.get("product_config_session", {})
is_public_user = request.env.user.has_group("base.group_public")
cfg_session_id = product_config_sessions.get(product_tmpl_id.id)
if cfg_session_id:
cfg_session = cfg_session_obj.browse(int(cfg_session_id))

# Retrieve an active configuration session or create a new one
if not cfg_session or not cfg_session.exists():
cfg_session = cfg_session_obj.sudo().create_get_session(
product_tmpl_id.id,
force_create=is_public_user,
user_id=request.env.user.id,
)
product_config_sessions.update({product_tmpl_id.id: cfg_session.id})
request.session["product_config_session"] = product_config_sessions

if cfg_session.user_id.has_group("base.group_public") and not is_public_user:
cfg_session.user_id = request.env.user
return cfg_session

@http.route()
def product(self, product, category="", search="", **kwargs):
# Use parent workflow for regular products
return super(ProductConfigWebsiteRestriction, self).product(
product, category, search, **kwargs
)

def convert_form_data(self, form_data):
"""convert the form data ptal to attribute"""
ProductAttributeLine = request.env['product.template.attribute.line']
form_values = []
ptal_count = 0

for item in form_data:
name = item.get('name')
value = item.get('value')

# Map product_template_id
if name == 'product_template_id':
form_values.append({'name': 'product_tmpl_id', 'value': value})

# Map product_id
if name == 'product_id':
form_values.append({'name': 'product_id', 'value': value})

# Map each ptal-* line to __attribute-<attribute_id>
elif name.startswith('ptal-') and value:
ptal_id = int(name.split('-')[1])
ptal = ProductAttributeLine.browse(ptal_id)
if ptal.exists():
attribute_id = ptal.attribute_id.id
form_values.append({'name': f'__attribute-{attribute_id}', 'value': value})
ptal_count += 1

# Optionally fill in empty attributes (not selected)
product_tmpl_id = next((i['value'] for i in form_data if i['name'] == 'product_template_id'), False)
if product_tmpl_id:
ptal_lines = ProductAttributeLine.search([('product_tmpl_id', '=', int(product_tmpl_id))])
for ptal in ptal_lines:
key = f'__attribute-{ptal.attribute_id.id}'
if not any(fv['name'] == key for fv in form_values):
form_values.append({'name': key, 'value': ''}) # empty value

form_values.append({'name': 'total_attributes', 'value': str(len(ptal_lines))})

print('\n\n form_values------------', form_values)
return form_values

def _prepare_configurator_values(self, form_vals, config_session_id):
"""Return dictionary of fields and values present
on configuration wizard"""
config_session_id = config_session_id.sudo()
product_tmpl_id = config_session_id.product_tmpl_id
config_fields = {
"state": config_session_id.state,
"config_session_id": config_session_id.id,
"product_tmpl_id": product_tmpl_id.id,
"product_preset_id": config_session_id.product_preset_id.id,
"price": config_session_id.price,
"value_ids": [[6, False, config_session_id.value_ids.ids]],
"attribute_line_ids": [
[4, line.id, False] for line in product_tmpl_id.attribute_line_ids
],
}
config_fields.update(form_vals)
return config_fields

def get_restrict_orm_form_vals(self, form_vals, config_session):
"""Return dictionary of dynamic field and its values
:param: form_vals: list of dictionary
Ex: [{'name': field-name, 'value': field-value},]
:param: cfg_session: record set of config session"""

product_tmpl_id = config_session.product_tmpl_id
values = {}
for form_val in form_vals:
dict_key = form_val.get("name", False)
dict_value = form_val.get("value", False)
if not dict_key or not dict_value:
continue
if dict_key not in values:
values.update({dict_key: []})
values[dict_key].append(dict_value)

product_configurator_obj = request.env["product.configurator"]
field_prefix = product_configurator_obj._prefixes.get("field_prefix")
custom_field_prefix = product_configurator_obj._prefixes.get(
"custom_field_prefix"
)

config_vals = {}
for attr_line in product_tmpl_id.attribute_line_ids.sorted():
attribute_id = attr_line.attribute_id.id
field_name = "%s%s" % (field_prefix, attribute_id)
custom_field = "%s%s" % (custom_field_prefix, attribute_id)

field_value = values.get(field_name, [])
field_value = [int(s) for s in field_value]
custom_field_value = values.get(custom_field, False)

if attr_line.custom and custom_field_value:
custom_field_value = custom_field_value[0]
if attr_line.attribute_id.custom_type in ["int", "float"]:
custom_field_value = safe_eval(custom_field_value)

if attr_line.multi:
field_value = [[6, False, field_value]]
else:
field_value = field_value and field_value[0] or False

config_vals.update(
{field_name: field_value, custom_field: custom_field_value}
)
return config_vals

def convert_data_domain(self, domain):
new_domain = {}

for key, domain_list in domain.items():
if not key.startswith('__attribute-') or not domain_list:
continue

domain_operator = domain_list[0][1]
value_ids = domain_list[0][2]

# Convert to records (even if empty list)
value_records = request.env['product.attribute.value'].browse(value_ids)

# Ensure we can fetch attribute
if value_records:
attribute = value_records[0].attribute_id
else:
# If no values, infer attribute ID from the key (e.g. '__attribute-2' → 2)
try:
attribute_id = int(key.replace('__attribute-', ''))
attribute = request.env['product.attribute'].browse(attribute_id)
except Exception:
continue # Skip if we can't safely parse

if not attribute or not attribute.exists():
continue

attribute_name = attribute.name
all_values = attribute.value_ids.mapped('name')
allowed_values = value_records.mapped('name') if value_records else []

new_domain[attribute_name] = [all_values, allowed_values, domain_operator]

return new_domain


@http.route(['/check/configurator/restriction'], type='json', auth="user", methods=['POST'])
def check_exist_product(self, product_template_id=False, attribute_id=False, ptav_id=False, form_data={}):
""" bypass custom value product create time from sale product configurator"""
product_configurator_obj = request.env["product.configurator"]
product_template_id = request.env['product.template'].browse(int(product_template_id))
print('\n\n product_template_id----------------', product_template_id)
if not product_template_id or not (product_template_id and product_template_id.config_ok):
print('\n\n False template----------------', product_template_id.config_ok)
return False
# prepare dictionary in formate needed to pass in onchage
form_values = self.convert_form_data(form_data)
attribute_id = request.env['product.attribute'].browse(int(attribute_id))

try:
config_session_id = self.get_config_session(product_tmpl_id=product_template_id)
except Exception:
pass

if config_session_id:
# prepare dictionary in formate needed to pass in onchage
form_values = self.get_restrict_orm_form_vals(form_values, config_session_id)
# Filter only keys starting with '__attribute-'
attribute_keys = [key for key in form_values if key.startswith('__attribute-')]
# Convert template value IDs to attribute value IDs
for key in attribute_keys:
ptav_id = form_values[key]
if ptav_id:
ptav = request.env['product.template.attribute.value'].browse(int(ptav_id))
pav_id = ptav.product_attribute_value_id.id if ptav.product_attribute_value_id else False
form_values[key] = pav_id # Replace the value in the original dict
# Config values
config_vals = self._prepare_configurator_values(form_values, config_session_id)

# call onchange
specs = product_configurator_obj._onchange_spec()
form_domain = {}
try:
field_name = f'__attribute-{attribute_id.id}'
form_domain = product_configurator_obj.sudo().apply_onchange_values(
values=config_vals, field_name=field_name, field_onchange=specs
)
form_domain['domain'] = self.convert_data_domain(form_domain['domain'])
form_domain['is_configured'] = product_template_id.config_ok
except Exception:
pass

print('\n\n form_domain----------------', form_domain)
return form_domain
26 changes: 26 additions & 0 deletions website_product_configurator_restriction/data/data_file.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding='UTF-8' ?>
<odoo noupdate="1">
<record id="ir_cron_delete_sessions_with_no_activity" model="ir.cron">
<field name="name">Website Configurator: Delete inactive config-sessions</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="model_id" ref="product_configurator.model_product_config_session" />
<field name="state">code</field>
<field name="code">model.remove_inactive_config_sessions()</field>
<field eval="True" name="active" />
</record>

<!-- Server Action For Create variants -->
<record id="server_action_create_variants" model="ir.actions.server">
<field name="name">Create Product Variants</field>
<field name="model_id" ref="product.model_product_template"/>
<field name="binding_model_id" ref="product.model_product_template"/>
<field name="binding_view_types">list,form</field>
<field eval="False" name="active" />
<field name="state">code</field>
<field name="code">
if records:
action = records._create_dynamic_variant_ids()
</field>
</record>
</odoo>
3 changes: 3 additions & 0 deletions website_product_configurator_restriction/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import product_template
from . import product_config
from . import sale_order
17 changes: 17 additions & 0 deletions website_product_configurator_restriction/models/product_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from datetime import timedelta

from odoo import api, fields, models


class ProductConfigSession(models.Model):
_inherit = "product.config.session"

def remove_inactive_config_sessions(self):
check_date = fields.Datetime.from_string(fields.Datetime.now()) - timedelta(
days=3
)
sessions_to_remove = self.search(
[("write_date", "<", fields.Datetime.to_string(check_date))]
)
if sessions_to_remove:
sessions_to_remove.unlink()
Loading
Loading