from odoo import models, fields, api, _ from odoo.exceptions import UserError from openai import OpenAI from datetime import datetime from bs4 import BeautifulSoup from markupsafe import escape # Import conditionnel de queue_job pour gérer les files d'attente si elles sont disponibles try: from odoo.addons.queue_job.job import job except ImportError: job = None # Si queue_job n'est pas disponible, job reste None class PartnerPurchaseAnalysisWizard(models.TransientModel): _name = 'partner.purchase.analysis.wizard' _description = 'Wizard for Partner Purchase Analysis with OpenAI' partner_id = fields.Many2one('res.partner', string="Customer", required=True, readonly=True) date_start = fields.Date(string="Start Date", required=True) date_end = fields.Date(string="End Date", default=fields.Date.today, required=True) def start_analysis(self): """Lance l'analyse en arrière-plan si `queue_job` est disponible, sinon exécute immédiatement.""" use_queue = self.env['ir.config_parameter'].sudo().get_param('my_module.use_queue_job') if use_queue and job: return self.with_delay().perform_analysis() else: return self.perform_analysis() def perform_analysis(self): """Effectue l'analyse des ventes pour le partenaire sélectionné.""" # Récupération de l'organisation et de la clé API OpenAI dans les paramètres organization = self.env['ir.config_parameter'].sudo().get_param('openai_connector.organization') api_key = self.env['ir.config_parameter'].sudo().get_param('openai_connector.api_key') if not api_key or not organization: raise UserError(_("API Key or Organization ID for OpenAI is missing in settings.")) client = OpenAI( api_key=api_key, organization=organization ) selected_categories = self.env.company.product_categories_analyzed if selected_categories: category_ids = selected_categories.ids domain = [ ('order_id.partner_id', '=', self.partner_id.id), ('product_id.categ_id', 'child_of', category_ids), ] else: domain = [('order_id.partner_id', '=', self.partner_id.id)] if self.date_start: domain.append(('order_id.date_order', '>=', self.date_start)) if self.date_end: domain.append(('order_id.date_order', '<=', self.date_end)) domain.append(('order_id.state', 'in', ['sale', 'done'])) sale_order_lines = self.env['sale.order.line'].search(domain) if not sale_order_lines: raise UserError(_("No relevant purchase history found for this customer based on the selected filters.")) # Préparation des données pour le prompt OpenAI purchase_data = {} for line in sale_order_lines: category_name = line.product_id.categ_id.name or "Uncategorized" if category_name not in purchase_data: purchase_data[category_name] = {} product_name = line.product_id.display_name if product_name not in purchase_data[category_name]: purchase_data[category_name][product_name] = [] purchase_data[category_name][product_name].append({ 'date': line.order_id.date_order, 'quantity': line.product_uom_qty, 'unit_price': line.price_unit, }) # Prompt défini en plusieurs lignes pour lisibilité user_lang = self.env.user.lang or 'en_US' user_lang_name = self.env['res.lang'].search([('code', '=', user_lang)], limit=1).name or "English" prompt = ( "Analyze all the sale order lines of the following customer. Identify trends, " "and any significant deviations. Respond in " f"{user_lang_name}. Produce the analysis adding next plan purchase or lost purchase. You output all " "in html format. Focus on missing product order and deviation from the average. " "Be sure to list all products and categories in the analysis. Try to identify " "the next purchase date for all product and warn if the customer is not buying " "and identify recurring sale and product not sale. Put in the analysis if the product is not bought " "in the last 12 months and show those product as lost sale with a value of the lost sales" ) # Construction de `purchase_details` pour le contenu du prompt purchase_details = f"Customer Purchase Analysis Grouped by Product Category and Product:\n\n" for category, products in purchase_data.items(): purchase_details += f"Category: {category}\n" for product, entries in products.items(): purchase_details += f" Product: {product}\n" for entry in entries: purchase_details += ( f" - Date: {entry['date']}, " f"Qty: {entry['quantity']}, " f"U.Price: {entry['unit_price']}\n" ) purchase_details += "\n" purchase_details += "\n" # Appel à l'API OpenAI try: response = client.chat.completions.create( messages=[ { "role": "system", "content": prompt }, { "role": "user", "content": purchase_details } ], model="gpt-4o", ) # Supposons que `response` contient la réponse complète au format HTML response_text = response.choices[0].message.content # Parse avec BeautifulSoup pour extraire le contenu du soup = BeautifulSoup(response_text, "html.parser") body_content = soup.body # Si body est présent, on utilise son contenu ; sinon, on utilise tout le texte cleaned_text = escape(body_content.get_text()) if body_content else escape(response_text) # Ajouter des balises de base pour structurer le texte en HTML simple html_content = f"
{cleaned_text.replace('\n', '
')}
" self.partner_id.sale_analysis = body_content self.partner_id.sale_analysis_date = fields.Datetime.today() # self.sale_analysis = response.choices[0].message.content # self.sale_analysis_date = fields.Datetime.today() print(response.choices[0].message.content) except Exception as e: raise UserError(_("Failed to get response from OpenAI. Error: %s") % str(e))