203 lines
No EOL
8.5 KiB
Python
203 lines
No EOL
8.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""OpenWebUI Product Integration Module
|
|
|
|
This module extends product.template functionality to integrate
|
|
OpenWebUI artificial intelligence into product management.
|
|
It enables automatic product category suggestions
|
|
based on description and characteristics analysis.
|
|
|
|
Main features:
|
|
- AI-powered category suggestions
|
|
- Suggestion history tracking
|
|
- Integrated user interface
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
from odoo import models, fields, _
|
|
from odoo.exceptions import UserError
|
|
from odoo.tools import float_round
|
|
from odoo.addons.openwebui_integration.models.openwebui_bot_mixin import OpenWebUIBotMixin
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
class ProductTemplate(models.Model, OpenWebUIBotMixin):
|
|
_inherit = "product.template"
|
|
|
|
suggested_category_id = fields.Many2one(
|
|
comodel_name='product.category',
|
|
string="Suggested Category",
|
|
readonly=True,
|
|
help="Category suggested by AI based on product information analysis"
|
|
)
|
|
|
|
suggestion_confidence = fields.Float(
|
|
string="Confidence",
|
|
readonly=True,
|
|
help="Confidence score (0-100) indicating how sure the AI is about the suggested category"
|
|
)
|
|
|
|
suggestion_date = fields.Datetime(
|
|
string="Suggestion Date",
|
|
readonly=True,
|
|
help="Date and time when the category was suggested by the AI"
|
|
)
|
|
|
|
suggestion_history_ids = fields.One2many(
|
|
comodel_name='product.category.suggestion.history',
|
|
inverse_name='product_id',
|
|
string="Suggestion History",
|
|
help="History of all category suggestions made by AI for this product"
|
|
)
|
|
|
|
def _generate_bot_message(self, records, values, command=None):
|
|
"""Generates the message to send to the bot to get category suggestions."""
|
|
# Préparer les données des produits
|
|
products_data = [{
|
|
'odoo_id': record.id, # ID interne Odoo
|
|
'name': record.name,
|
|
'description': record.description or '',
|
|
'description_sale': record.description_sale or '',
|
|
'default_code': record.default_code or '',
|
|
'current_category': record.categ_id.display_name,
|
|
'sellers': [
|
|
{
|
|
'name': seller.partner_id.display_name,
|
|
'product_code': seller.product_code or '',
|
|
'product_name': seller.product_name or ''
|
|
} for seller in record.seller_ids
|
|
]
|
|
} for record in records]
|
|
|
|
# Récupérer toutes les catégories disponibles
|
|
Category = self.env['product.category']
|
|
categories = Category.search([('parent_id', '!=', False)], order='complete_name')
|
|
available_categories = [{
|
|
'id': cat.id,
|
|
'name': cat.name,
|
|
'complete_name': cat.complete_name or cat.name,
|
|
'level': len(cat.parent_path.split('/')) - 1 if cat.parent_path else 0
|
|
} for cat in categories]
|
|
|
|
# Construire le message pour l'IA
|
|
message = {
|
|
'task': 'product_categorization',
|
|
'products': products_data,
|
|
'available_categories': available_categories,
|
|
'instructions': """For each product in the products list, analyze the product information and suggest the most appropriate product category from the available list.
|
|
Consider each product's name, description, and supplier information to make the best match.
|
|
Return a list of JSON objects, one for each product, with:
|
|
- 'odoo_id' (integer): The internal Odoo ID of the product
|
|
- 'category_id' (integer): The ID of the most appropriate category
|
|
- 'confidence' (float between 0 and 100): How confident you are about this suggestion
|
|
- 'explanation' (string): A detailed explanation of why this category was chosen, including analysis of the product name, description, and other relevant information.""",
|
|
'format': 'json'
|
|
}
|
|
return json.dumps(message)
|
|
|
|
def _process_bot_response(self, values, response):
|
|
"""Process the bot response to extract the suggested category."""
|
|
try:
|
|
results = json.loads(response)
|
|
if not isinstance(results, list):
|
|
raise ValueError("La réponse n'est pas une liste JSON valide")
|
|
|
|
for result in results:
|
|
category_id = result.get('category_id')
|
|
confidence = result.get('confidence', 0.0)
|
|
product_id = result.get('odoo_id')
|
|
|
|
if not category_id:
|
|
raise ValueError("No category ID in response")
|
|
|
|
if not product_id:
|
|
raise ValueError("No product ID in response")
|
|
|
|
# Vérifier que la catégorie existe
|
|
category = self.env['product.category'].browse(category_id).exists()
|
|
if not category:
|
|
raise ValueError(f"Category {category_id} not found")
|
|
|
|
# Trouver le produit concerné
|
|
product = self.filtered(lambda p: p.id == product_id)
|
|
if not product:
|
|
raise ValueError(f"Product {product_id} not found in selection")
|
|
|
|
# Mettre à jour les valeurs pour ce produit
|
|
product.write({
|
|
'suggested_category_id': category_id,
|
|
'suggestion_confidence': confidence,
|
|
'suggestion_date': fields.Datetime.now(),
|
|
})
|
|
|
|
# Créer l'historique
|
|
self.env['product.category.suggestion.history'].create({
|
|
'product_id': product_id,
|
|
'suggested_category_id': category_id,
|
|
'suggestion_confidence': confidence,
|
|
'input_data': json.dumps(result),
|
|
'explanation': result.get('explanation', ''),
|
|
'applied': False
|
|
})
|
|
|
|
except Exception as e:
|
|
raise UserError(_("Erreur lors du traitement de la réponse de l'IA: %s") % str(e))
|
|
|
|
return values
|
|
|
|
def action_suggest_category(self):
|
|
"""Request category suggestions from AI."""
|
|
if len(self) > 800:
|
|
raise UserError(_("For performance reasons, you cannot analyze more than 800 products at once."))
|
|
|
|
# Get company settings
|
|
company = self.env.company
|
|
if not company.openwebui_enabled:
|
|
raise UserError(_("OpenWebUI is not enabled for your company. Please enable it in company settings."))
|
|
|
|
model = company.openwebui_default_model_id
|
|
if not model:
|
|
raise UserError(_("No default OpenWebUI model configured. Please configure it in company settings."))
|
|
|
|
# Process products in batches
|
|
batch_size = company.openwebui_products_per_request
|
|
successful_products = self.env['product.template']
|
|
|
|
for i in range(0, len(self), batch_size):
|
|
batch = self[i:i + batch_size]
|
|
|
|
# Créer une nouvelle transaction pour ce batch
|
|
with self.env.cr.savepoint():
|
|
try:
|
|
values = {'bot': model}
|
|
self._apply_logic(batch, values)
|
|
# Si on arrive ici, le batch a réussi
|
|
successful_products |= batch
|
|
_logger.info('Successfully processed batch of %d products: %s',
|
|
len(batch), batch.mapped('default_code'))
|
|
except Exception as e:
|
|
_logger.error('Batch processing failed for products %s: %s',
|
|
batch.mapped('default_code'), str(e))
|
|
# Le savepoint sera rollback automatiquement
|
|
continue
|
|
|
|
# Si aucun produit n'a été traité avec succès
|
|
if not successful_products:
|
|
raise UserError(_('No suggestions could be generated by AI.'))
|
|
|
|
# Ouvrir l'assistant si des suggestions ont été générées
|
|
if any(product.suggested_category_id for product in successful_products):
|
|
wizard = self.env['product.category.suggestion.wizard'].create({})
|
|
return {
|
|
'name': _('Category Suggestions'),
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'product.category.suggestion.wizard',
|
|
'res_id': wizard.id,
|
|
'view_mode': 'form',
|
|
'target': 'new',
|
|
'context': self.env.context,
|
|
}
|
|
else:
|
|
raise UserError(_('No valid suggestions could be generated by AI.')) |