new modules openai

This commit is contained in:
Benoît Vézina 2024-11-16 06:45:41 -05:00
parent e7fd3c3910
commit 4163232371
28 changed files with 771 additions and 0 deletions

View file

@ -0,0 +1,2 @@
from . import models

View file

@ -0,0 +1,15 @@
{
'name': 'Customer Itch Cycle Management',
'version': '1.0',
'depends': ['base', 'sale'],
'author': 'Your Name',
'category': 'Sales Management',
'description': "Manage customer itch cycles by product for proactive sales engagement.",
'data': [
'views/itch_cycle_product_partner_view.xml',
'views/res_partner_view.xml',
],
'installable': True,
'application': False,
}

View file

@ -0,0 +1,3 @@
from . import itch_cycle_product_partner
from . import sale_order

View file

@ -0,0 +1,33 @@
from odoo import models, fields, api
from datetime import timedelta, datetime
class ItchCycleProductPartner(models.Model):
_name = 'itch_cycle_product_partner'
_description = 'Itch Cycle by Product and Partner'
partner_id = fields.Many2one('res.partner', string="Client", required=True, ondelete='cascade')
product_id = fields.Many2one('product.product', string="Produit", required=True)
last_purchase_date = fields.Date(string="Dernière Date d'Achat")
itch_cycle_duration = fields.Integer(string="Durée du Itch-Cycle (jours)", default=0)
next_follow_up_date = fields.Date(string="Prochaine Date de Suivi", compute="_compute_next_follow_up_date", store=True)
@api.depends('last_purchase_date', 'itch_cycle_duration')
def _compute_next_follow_up_date(self):
for record in self:
if record.last_purchase_date and record.itch_cycle_duration > 0:
record.next_follow_up_date = record.last_purchase_date + timedelta(days=record.itch_cycle_duration)
else:
record.next_follow_up_date = False
class ResPartner(models.Model):
_inherit = 'res.partner'
itch_cycle_product_ids = fields.One2many('itch_cycle_product_partner', 'partner_id', string="Itch Cycles Produits")
itch_next_delay = fields.Date(string="Prochaine Date de Suivi (Itch-Cycle Min)", compute="_compute_itch_next_delay", store=True)
@api.depends('itch_cycle_product_ids.next_follow_up_date')
def _compute_itch_next_delay(self):
for partner in self:
follow_up_dates = partner.itch_cycle_product_ids.mapped('next_follow_up_date')
partner.itch_next_delay = min(follow_up_dates) if follow_up_dates else False

View file

@ -0,0 +1,44 @@
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from datetime import datetime
class SaleOrder(models.Model):
_inherit = 'sale.order'
def action_confirm(self):
super(SaleOrder, self).action_confirm()
for line in self.order_line:
partner_id = self.partner_id
product_id = line.product_id
itch_cycle_record = self.env['itch_cycle_product_partner'].search([
('partner_id', '=', partner_id.id),
('product_id', '=', product_id.id)
], limit=1)
if itch_cycle_record:
if itch_cycle_record.last_purchase_date:
days_since_last_purchase = (datetime.now().date() - itch_cycle_record.last_purchase_date).days
if itch_cycle_record.itch_cycle_duration == 0:
raise UserError(_(
f"Veuillez définir la durée du itch-cycle pour le produit '{product_id.name}' "
f"pour le client '{partner_id.name}'.\n\n"
f"Il y a eu {days_since_last_purchase} jours depuis le dernier achat."
))
else:
itch_cycle_record.last_purchase_date = datetime.now().date()
itch_cycle_record.itch_cycle_duration = days_since_last_purchase
else:
itch_cycle_record.last_purchase_date = datetime.now().date()
else:
new_record = self.env['itch_cycle_product_partner'].create({
'partner_id': partner_id.id,
'product_id': product_id.id,
'last_purchase_date': datetime.now().date(),
'itch_cycle_duration': 0
})
raise UserError(_(
f"Il n'y a pas encore de itch-cycle défini pour le produit '{product_id.name}' "
f"avec le client '{partner_id.name}'.\n\nVeuillez entrer une durée pour ce cycle."
))

View file

@ -0,0 +1,44 @@
<odoo>
<record id="view_itch_cycle_product_partner_tree" model="ir.ui.view">
<field name="name">itch.cycle.product.partner.tree</field>
<field name="model">itch_cycle_product_partner</field>
<field name="arch" type="xml">
<tree>
<field name="partner_id"/>
<field name="product_id"/>
<field name="last_purchase_date"/>
<field name="itch_cycle_duration"/>
<field name="next_follow_up_date"/>
</tree>
</field>
</record>
<record id="view_itch_cycle_product_partner_form" model="ir.ui.view">
<field name="name">itch.cycle.product.partner.form</field>
<field name="model">itch_cycle_product_partner</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="partner_id"/>
<field name="product_id"/>
<field name="last_purchase_date"/>
<field name="itch_cycle_duration"/>
<field name="next_follow_up_date"/>
</group>
</sheet>
</form>
</field>
</record>
<menuitem id="menu_itch_cycle_product_partner" name="Itch Cycles"
parent="sale.sale_order_menu"
action="action_itch_cycle_product_partner"/>
<record id="action_itch_cycle_product_partner" model="ir.actions.act_window">
<field name="name">Itch Cycles</field>
<field name="res_model">itch_cycle_product_partner</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>

View file

@ -0,0 +1,23 @@
<odoo>
<record id="view_partner_form_inherit_itch_cycle" model="ir.ui.view">
<field name="name">res.partner.form.itch.cycle</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<sheet position="before">
<group string="Itch Cycle Information">
<field name="itch_next_delay" readonly="1"/>
<field name="itch_cycle_product_ids" readonly="1">
<tree>
<field name="product_id"/>
<field name="last_purchase_date"/>
<field name="itch_cycle_duration"/>
<field name="next_follow_up_date"/>
</tree>
</field>
</group>
</sheet>
</field>
</record>
</odoo>

View file

@ -0,0 +1 @@
from . import models

View file

@ -0,0 +1,22 @@
{
'name': 'OpenAI Connector',
'version': '1.0',
'category': 'Tools',
'summary': 'Manage OpenAI connection settings',
'license': 'AGPL-3',
'description': '''
This module allows the configuration of OpenAI API connection
settings, including API key and Organization ID.
''',
'author': 'Bemade Inc.',
'depends': ['base_setup'],
'data': [
'views/res_config_settings_views.xml',
'security/ir.model.access.csv',
],
'external_dependencies': {
'python': ['openai'],
},
'installable': True,
'application': False,
}

View file

@ -0,0 +1,2 @@
from . import res_config_settings
from . import res_company

View file

@ -0,0 +1,9 @@
from odoo import models, fields
class ResCompany(models.Model):
_inherit = 'res.company'
api_key = fields.Char(string="API Key", help="API Key for OpenAI specific to this company. It should start with 'sk-'")
organization = fields.Char(string="Organization ID", help="Organization ID for OpenAI specific to this company. It should start with 'org-'")

View file

@ -0,0 +1,96 @@
from odoo import models, fields, api, _
from odoo.exceptions import UserError
import openai
import logging
_logger = logging.getLogger(__name__)
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
api_key = fields.Char(
string="API Key",
config_parameter='openai_connector.api_key',
help="API Key for OpenAI, used as a default if the company-specific key is not set. Format should start with 'sk-'"
)
organization = fields.Char(
string="Organization ID",
config_parameter='openai_connector.organization',
help="Organization ID for OpenAI, used as a default if the company-specific ID is not set. Format should start with 'org-'"
)
connection_status = fields.Char(
string="Connection Status",
compute='_compute_connection_status',
help="Displays the current connection status with OpenAI."
)
@api.depends('api_key', 'organization')
def _compute_connection_status(self):
for record in self:
try:
if record.api_key and record.organization: # Vérifie que les champs ne sont pas vides
record._test_openai_connection()
record.connection_status = "Connected"
else:
record.connection_status = "Disconnected"
except Exception as e:
record.connection_status = "Disconnected"
_logger.error(f"OpenAI connection test failed: {str(e)}")
def set_values(self):
super(ResConfigSettings, self).set_values()
self.env['ir.config_parameter'].sudo().set_param(
'openai_connector.api_key', self.api_key)
self.env['ir.config_parameter'].sudo().set_param(
'openai_connector.organization', self.organization)
# Test de connexion automatique lors de l'enregistrement si les champs sont remplis
if self.api_key and self.organization:
self._test_openai_connection()
@api.model
def get_values(self):
res = super(ResConfigSettings, self).get_values()
res.update(
api_key=self.env['ir.config_parameter'].sudo().get_param(
'openai_connector.api_key', default=''),
organization=self.env['ir.config_parameter'].sudo().get_param(
'openai_connector.organization', default='')
)
return res
def _test_openai_connection(self):
"""Method to test connection to OpenAI."""
if not self.api_key or not self.organization:
return # Ne fait rien si l'un des champs est vide
try:
client = openai.OpenAI(organization=self.organization, api_key=self.api_key)
client.models.list() # Test basique de la connexion
except Exception as e:
raise UserError(_("Failed to connect to OpenAI API. Please check the API Key and Organization ID.\nError: %s") % str(e))
def action_test_openai_connection(self):
"""Action to test connection manually and display the result."""
try:
self._test_openai_connection()
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _("Connection Test Successful"),
'message': _("The connection to OpenAI was successful."),
'sticky': False,
},
}
except UserError as e:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _("Connection Test Failed"),
'message': str(e),
'sticky': True,
},
}

View file

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_res_config_settings_openai,res.config.settings.openai,model_res_config_settings,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_res_config_settings_openai res.config.settings.openai model_res_config_settings base.group_system 1 1 1 1

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_openai_connector_config_settings_form" model="ir.ui.view">
<field name="name">openai.connector.config.settings.form</field>
<field name="model">res.config.settings</field>
<field name="priority" eval="30"/>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
<field name="arch" type="xml">
<!-- Insertion sous les paramètres généraux -->
<xpath expr="//block[@name='integration']" position="after">
<block title="OpenAI Integration" name="openai_connector_settings">
<!-- Champ pour l'API Key OpenAI -->
<setting id="openai_api_key_setting"
help="Enter the API Key for OpenAI. This will be used by default if the company-specific key is not set."
company_dependent="1">
<field name="api_key"/>
</setting>
<!-- Champ pour l'Organization ID OpenAI -->
<setting id="openai_organization_setting"
help="Enter the Organization ID for OpenAI. This will be used by default if the company-specific ID is not set."
company_dependent="1">
<field name="organization"/>
</setting>
<!-- Champ affichant le statut de connexion -->
<setting id="openai_connection_status_setting"
help="Displays the current connection status with OpenAI."
company_dependent="1">
<field name="connection_status" readonly="1"/>
</setting>
<!-- Bouton pour tester la connexion -->
<div class="oe_button_box" name="button_box">
<button name="action_test_openai_connection"
string="Tester la connexion"
type="object"
class="btn-primary oe_stat_button"
icon="fa-check-circle"/>
</div>
</block>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,2 @@
from . import models
from . import wizard

View file

@ -0,0 +1,31 @@
{
'name': 'Partner Purchase Analysis with Optional OpenAI and Queue Job',
'version': '1.0',
'category': 'Tools',
'summary': 'Manage OpenAI connection settings',
'license': 'AGPL-3',
'description': '''
This module allows the configuration of OpenAI API connection
settings, including API key and Organization ID.
''',
'author': 'Bemade Inc.',
'depends': [
'base',
'sale',
'product',
'openai_connector',
'sale_management',
],
'data': [
'security/ir.model.access.csv', # Fichier de sécurité mis à jour
'data/queue_job_group.xml',
'views/res_config_settings_view.xml',
'views/res_partner_view.xml',
'wizard/partner_purchase_analysis_wizard_view.xml',
],
'external_dependencies': {
'python': ['openai'],
},
'installable': True,
'application': False,
}

View file

@ -0,0 +1,7 @@
<odoo>
<record id="group_use_queue_job" model="res.groups">
<field name="name">Use Queue Job for Asynchronous Processing</field>
<field name="category_id" ref="base.module_category_tools"/>
</record>
</odoo>

View file

@ -0,0 +1,4 @@
from . import res_partner
from . import partner_purchase_analysis_wizard
from . import res_config_settings
from . import res_company

View file

@ -0,0 +1,93 @@
from odoo import models, fields, api, _
from odoo.exceptions import UserError
import openai
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")
date_end = fields.Date(string="End Date")
analysis_result = fields.Text(string="Analysis Result", readonly=True)
def start_analysis(self):
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é."""
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))
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 l'API d'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,
})
# Construire le prompt pour OpenAI
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"
purchase_details = f"Customer Purchase Analysis Grouped by Product Category and Product (Response in {user_lang_name}):\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"
try:
response = openai.ChatCompletion.create(
model="gpt-4",
messages=[
{"role": "system",
"content": f"Analyze the following customer purchase history. Identify trends, product category preferences, and any significant deviations. Respond in {user_lang_name}."},
{"role": "user", "content": purchase_details}
]
)
self.analysis_result = response.choices[0].message['content']
except Exception as e:
raise UserError(_("Failed to get response from OpenAI. Error: %s") % str(e))

View file

@ -0,0 +1,11 @@
from odoo import models, fields
class ResCompany(models.Model):
_inherit = 'res.company'
product_categories_analyzed = fields.Many2many(
'product.category',
string="Product Categories Analyzed",
help="Select product categories to include in the purchase analysis. Only products in these categories and their subcategories will be analyzed."
)

View file

@ -0,0 +1,36 @@
from odoo import models, fields, api
from odoo.modules.module import get_module_resource
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
use_queue_job = fields.Boolean(
string="Use Queue Job for Asynchronous Processing",
help="If enabled, the system will use queue jobs for background tasks. Requires the queue_job module.",
default=False
)
product_categories_analyzed = fields.Many2many(
related='company_id.product_categories_analyzed',
comodel_name='product.category',
string="Product Categories Analyzed",
help="Select product categories to include in the purchase analysis."
)
@api.model
def get_values(self):
res = super().get_values()
res.update(
use_queue_job=self.env['ir.config_parameter'].sudo().get_param('my_module.use_queue_job', default=False)
)
return res
def set_values(self):
super().set_values()
self.env['ir.config_parameter'].sudo().set_param('my_module.use_queue_job', self.use_queue_job)
@api.model
def enable_queue_job_group(self):
"""Enable the group if the queue_job module is installed"""
group = self.env.ref('partner_purchase_analysis_with_openai_filtered.group_use_queue_job', raise_if_not_found=False)
if group and get_module_resource('queue_job'):
group.sudo().write({'users': [(4, self.env.user.id)]})

View file

@ -0,0 +1,20 @@
from odoo import models, fields
class ResPartner(models.Model):
_inherit = 'res.partner'
sale_analysis = fields.Html("Sale Analysis", help="Analysis of the partner's sales.")
sale_analysis_date = fields.Date("Sale Analysis Date", help="Date of the latest sale analysis.")
def action_open_purchase_analysis(self):
"""Ouvre le wizard d'analyse des achats pour le client actuel."""
return {
'name': 'Purchase Analysis',
'type': 'ir.actions.act_window',
'res_model': 'partner.purchase.analysis.wizard',
'view_mode': 'form',
'target': 'new',
'context': {
'default_partner_id': self.id,
},
}

View file

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_partner_purchase_analysis_wizard_sales,partner.purchase.analysis.wizard access,model_partner_purchase_analysis_wizard,sales_team.group_sale_salesman,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_partner_purchase_analysis_wizard_sales partner.purchase.analysis.wizard access model_partner_purchase_analysis_wizard sales_team.group_sale_salesman 1 1 1 1

View file

@ -0,0 +1,19 @@
<odoo>
<record id="res_config_settings_view_form_inherit_partner_analysis" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.partner.analysis</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="openai_connector.view_openai_connector_config_settings_form"/>
<field name="arch" type="xml">
<xpath expr="//block[@name='openai_connector_settings']" position="inside">
<div class="col-12" groups="openai_partner_purchase_analysis.group_use_queue_job">
<label for="use_queue_job"/>
<field name="use_queue_job"/>
</div>
<div class="col-12 mt16">
<label for="product_categories_analyzed"/>
<field name="product_categories_analyzed" widget="many2many_tags"/>
</div>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,17 @@
<odoo>
<record id="view_partner_form_inherit_purchase_analysis" model="ir.ui.view">
<field name="name">res.partner.form.inherit.purchase.analysis</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<notebook position="inside">
<page string="Sales Analysis">
<group>
<field name="sale_analysis" widget="html" placeholder="No analysis available"/>
<field name="sale_analysis_date" placeholder="Date of analysis"/>
</group>
</page>
</notebook>
</field>
</record>
</odoo>

View file

@ -0,0 +1 @@
from . import partner_purchase_analysis_wizard

View file

@ -0,0 +1,141 @@
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))
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 the following customer purchase history. Identify trends, product "
"category preferences, and any significant deviations. Respond in "
f"{user_lang_name}. Produce graph and table of the analysis. You output all "
"in html format."
)
# 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 <body>
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"<div>{cleaned_text.replace('\n', '<br/>')}</div>"
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))

View file

@ -0,0 +1,45 @@
<odoo>
<record id="view_partner_purchase_analysis_wizard_form" model="ir.ui.view">
<field name="name">partner.purchase.analysis.wizard.form</field>
<field name="model">partner.purchase.analysis.wizard</field>
<field name="arch" type="xml">
<form string="Analyze Purchases with OpenAI">
<group>
<field name="partner_id" readonly="1"/>
<field name="date_start"/>
<field name="date_end"/>
<button name="start_analysis" string="Run Analysis" type="object" class="btn-primary"/>
</group>
</form>
</field>
</record>
<!-- Action serveur pour ouvrir le wizard d'analyse des achats -->
<record id="action_partner_purchase_analysis" model="ir.actions.server">
<field name="name">Analyze Purchases</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="state">code</field>
<field name="binding_type">action</field>
<field name="binding_model_id" ref="base.model_res_partner"/>
<field name="code">
action = {
'name': 'Purchase Analysis',
'type': 'ir.actions.act_window',
'res_model': 'partner.purchase.analysis.wizard',
'view_mode': 'form',
'target': 'new',
'context': {
'default_partner_id': env.context.get('active_id'),
}
}
</field>
</record>
<!-- Ajouter l'action dans le menu "Actions" de res.partner -->
<!-- <record id="action_partner_purchase_analysis_act_window" model="ir.actions.servers">-->
<!-- <field name="name">Analyze Purchases</field>-->
<!-- <field name="model">res.partner</field>-->
<!-- <field name="key2">client_action_multi</field>-->
<!-- <field name="value" eval="'ir.actions.server,' + str(ref('openai_partner_purchase_analysis.action_partner_purchase_analysis'))"/>-->
<!-- </record>-->
</odoo>