itch et unifi

This commit is contained in:
xtremxpert 2024-12-17 12:51:45 -05:00
parent 9578594628
commit 536f10b61e
10 changed files with 221 additions and 51 deletions

View file

@ -33,10 +33,7 @@ class Company(models.Model):
("specific", "Specific Vendors"), ("specific", "Specific Vendors"),
], ],
default="all", default="all",
help=( help="Choose whether to apply overdue warnings to all vendors or only to specific vendors.",
"Choose whether to apply overdue warnings to all vendors or only to "
"specific vendors."
),
) )
warn_supplier_specific_ids = fields.Many2many( warn_supplier_specific_ids = fields.Many2many(

View file

@ -20,6 +20,36 @@ class ItchCycleProductPartner(models.Model):
required=True required=True
) )
quantity_total_ordered = fields.Float(
string="Quantité totale commandée",
compute="_compute_quantity_total_ordered",
store=True
)
quantity_of_orders = fields.Integer(
string="Nombre de commandes",
compute="_compute_quantity_of_orders",
store=True
)
min_quantity_ordered = fields.Float(
string="Quantité minimale commandée",
compute="_compute_min_quantity_ordered",
store=True
)
max_quantity_ordered = fields.Float(
string="Quantité maximale commandée",
compute="_compute_max_quantity_ordered",
store=True
)
mean_quantity_ordered = fields.Float(
string="Quantité moyenne commandée",
compute="_compute_mean_quantity_ordered",
store=True
)
average_cycle = fields.Integer( average_cycle = fields.Integer(
string="Cycle moyen (jours)", string="Cycle moyen (jours)",
compute="_compute_average_cycle", compute="_compute_average_cycle",
@ -142,7 +172,10 @@ class ItchCycleProductPartner(models.Model):
""" """
# Récupérer toutes les lignes de commandes confirmées # Récupérer toutes les lignes de commandes confirmées
sale_order_lines = self.env['sale.order.line'].search([ sale_order_lines = self.env['sale.order.line'].search([
('order_id.state', 'in', ['sale', 'done']) # Commandes confirmées ou terminées ('order_id.state', 'in', ['sale', 'done']), # Commandes confirmées ou terminées
('qty_delivered', '>', 0), # Quantité livrée supérieure à 0
('product_id', '!=', False), # Exclure les lignes sans produit
('order_id.partner_id', '=', 5024659), # un client pour test
]) ])
# Carte pour regrouper par couple (client, produit) # Carte pour regrouper par couple (client, produit)
@ -193,3 +226,23 @@ class ItchCycleProductPartner(models.Model):
'sale_order_line_ids': [(6, 0, data['sale_order_line_ids'])], # Lier ou actualiser les lignes 'sale_order_line_ids': [(6, 0, data['sale_order_line_ids'])], # Lier ou actualiser les lignes
}) })
return True return True
def _compute_quantity_total_ordered(self):
for record in self:
record.quantity_total_ordered = sum(record.sale_order_line_ids.mapped('product_uom_qty'))
def _compute_quantity_of_orders(self):
for record in self:
record.quantity_of_orders = len(record.sale_order_line_ids)
def _compute_min_quantity_ordered(self):
for record in self:
record.min_quantity_ordered = min(record.sale_order_line_ids.mapped('product_uom_qty'))
def _compute_max_quantity_ordered(self):
for record in self:
record.max_quantity_ordered = max(record.sale_order_line_ids.mapped('product_uom_qty'))
def _compute_mean_quantity_ordered(self):
for record in self:
record.mean_quantity_ordered = np.mean(record.sale_order_line_ids.mapped('product_uom_qty'))

View file

@ -11,9 +11,16 @@ class ResPartner(models.Model):
string="Itch Cycles Produits" string="Itch Cycles Produits"
) )
reorder_cycle_product_ids = fields.One2many(
comodel_name='itch.cycle.product.partner',
inverse_name='partner_id',
string="Itch Cycles Produits",
domain=[('quantity_of_orders', '>', 1)]
)
itch_next_delay = fields.Date( itch_next_delay = fields.Date(
string="Prochaine Date de Suivi (Itch-Cycle Min)", string="Prochaine Date de Suivi (Itch-Cycle Min)",
compute="_compute_itch_next_delay", # compute="_compute_itch_next_delay",
store=True store=True
) )
@ -23,8 +30,11 @@ class ResPartner(models.Model):
Calcule la date de suivi minimale parmi tous les cycles de produits associés. Calcule la date de suivi minimale parmi tous les cycles de produits associés.
""" """
for partner in self: for partner in self:
follow_up_dates = partner.itch_cycle_product_ids.mapped('next_follow_up_date') follow_up_dates = partner.reorder_cycle_product_ids.mapped('next_follow_up_date')
partner.itch_next_delay = min(follow_up_dates) if follow_up_dates else False if follow_up_dates:
partner.itch_next_delay = min(follow_up_dates)
else:
partner.itch_next_delay = False
def action_populate_itch_cycles(self): def action_populate_itch_cycles(self):
""" """

View file

@ -8,20 +8,24 @@ class SaleOrderLine(models.Model):
string="Cycle Produit/Partenaire" string="Cycle Produit/Partenaire"
) )
@api.model @api.model_create_multi
def create(self, vals): def create(self, vals_list):
line = super(SaleOrderLine, self).create(vals) # Appeler le super pour créer les lignes
# Associer au bon cycle produit/partenaire lines = super().create(vals_list)
if vals.get('product_id') and vals.get('order_id'):
partner_id = self.env['sale.order'].browse(vals['order_id']).partner_id.id # Associer les lignes nouvellement créées avec leur cycle
itch_cycle = self.env['itch.cycle.product.partner'].search([ for line in lines:
('partner_id', '=', partner_id), if line.product_id and line.order_id:
('product_id', '=', vals['product_id']) partner_id = line.order_id.partner_id.id
], limit=1) itch_cycle = self.env['itch.cycle.product.partner'].search([
if not itch_cycle: ('partner_id', '=', partner_id),
itch_cycle = self.env['itch.cycle.product.partner'].create({ ('product_id', '=', line.product_id.id)
'partner_id': partner_id, ], limit=1)
'product_id': vals['product_id'], if not itch_cycle:
}) itch_cycle = self.env['itch.cycle.product.partner'].create({
line.itch_cycle_id = itch_cycle.id 'partner_id': partner_id,
return line 'product_id': line.product_id.id,
})
line.itch_cycle_id = itch_cycle.id
return lines

View file

@ -36,6 +36,12 @@
<field name="last_purchase_date" readonly="1"/> <field name="last_purchase_date" readonly="1"/>
<field name="next_expected_date" readonly="1"/> <field name="next_expected_date" readonly="1"/>
<field name="next_follow_up_date"/> <field name="next_follow_up_date"/>
<field name="prediction_status" widget="statusbar" options="{'on_time': 'green', 'delayed': 'red'}"/>
<field name="quantity_of_orders" readonly="1"/>
<field name="quantity_total_ordered" readonly="1"/>
<field name="min_quantity_ordered" readonly="1"/>
<field name="max_quantity_ordered" readonly="1"/>
<field name="mean_quantity_ordered" readonly="1"/>
</group> </group>
</group> </group>
<notebook> <notebook>

View file

@ -13,15 +13,20 @@
class="btn-primary"/> class="btn-primary"/>
<group string="Itch Cycle Information"> <group string="Itch Cycle Information">
<field name="itch_next_delay" readonly="1"/> <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> </group>
<field name="reorder_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"/>
<field name="min_quantity_ordered"/>
<field name="mean_quantity_ordered"/>
<field name="max_quantity_ordered"/>
<field name="quantity_of_orders"/>
<field name="quantity_total_ordered"/>
</tree>
</field>
</page> </page>
</notebook> </notebook>
</field> </field>

View file

@ -1,8 +1,6 @@
from .unifi_client import Unifi
from odoo import models, fields, api from odoo import models, fields, api
from .unifi_client import Unifi
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
#from unificontrol import UnifiClient
class UnifiCtrl(models.Model): class UnifiCtrl(models.Model):
@ -38,10 +36,26 @@ class UnifiCtrl(models.Model):
help="Password for API authentication." help="Password for API authentication."
) )
controller_type = fields.Selection(
[
('udm', 'UDM Pro'),
('controller', 'Controller')
],
string="Controller Type",
default='udm',
required=True,
help="Select whether this is a UDM Pro or a Unifi Controller."
)
def action_test_connection(self): def action_test_connection(self):
"""Test connection to Unifi Controller.""" """Test connection to Unifi Controller or UDM Pro."""
self.ensure_one() self.ensure_one()
client = Unifi(self.ip_address, self.username, self.password) client = Unifi(
host=self.ip_address,
username=self.username,
password=self.password,
is_udm=(self.controller_type == 'udm')
)
try: try:
client.login() client.login()
return { return {
@ -54,4 +68,46 @@ class UnifiCtrl(models.Model):
}, },
} }
except Exception as e: except Exception as e:
raise ValidationError(f"Connection failed: {e}") raise ValidationError(f"Connection failed: {e}")
def action_fetch_networks_and_fill_rules(self):
"""Fetch networks and populate firewall rules."""
self.ensure_one()
client = Unifi(
host=self.ip_address,
username=self.username,
password=self.password,
is_udm=(self.controller_type == 'udm')
)
try:
client.login()
networks = client.get_networks()
firewall_rule_model = self.env['firewall.rule']
for network in networks:
rule_name = network.get('name', 'Unnamed Rule')
src_ip = network.get('subnet', None) # Example: Adjust based on API data
if not src_ip:
continue
firewall_rule_model.create({
'name': f"Network: {rule_name}",
'action': 'accept',
'enabled': True,
'src_ip': src_ip,
'dst_ip': None,
'log': False,
'controller_id': self.id,
})
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'Networks Fetched',
'message': f"{len(networks)} networks fetched and rules created.",
'type': 'success',
},
}
except Exception as e:
raise ValidationError(f"Failed to fetch networks: {e}")

View file

@ -9,7 +9,7 @@ class FirewallRule(models.Model):
required=True required=True
) )
ctrl_id = fields.Many2one( controller_id = fields.Many2one(
'unifi.ctrl', 'unifi.ctrl',
string='Controller', string='Controller',
required=True, required=True,

View file

@ -2,24 +2,37 @@ import requests
import urllib3 import urllib3
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
# API Endpoints
UNIFI_LOGIN_PATH = '/api/auth/login' UNIFI_LOGIN_PATH = '/api/auth/login'
UNIFI_SITES_PATH = '/api/self/sites' UNIFI_SITES_PATH = '/api/self/sites'
UNIFI_FIREWALL_RULES_PATH = '/api/s/default/rest/firewallrule'
UNIFI_NETWORKS_PATH = '/api/s/default/rest/network'
UNIFI_WIFIS_PATH = '/api/s/default/rest/wlanconf'
# Désactiver les avertissements SSL pour certificats auto-signés # Prefix for UDM Pro endpoints
UDM_PROXY_PREFIX = '/proxy/network'
# Disable SSL verification warnings
urllib3.disable_warnings() urllib3.disable_warnings()
class Unifi: class Unifi:
def __init__(self, host, username, password): def __init__(self, host, username, password, is_udm=False):
"""Initialize Unifi client.""" """Initialize Unifi client."""
self.host = host self.host = host
self.username = username self.username = username
self.password = password self.password = password
self.is_udm = is_udm
self.session = requests.Session() self.session = requests.Session()
self.csrf = "" self.csrf = ""
def _get_url(self, path):
"""Construct the correct URL based on the type of controller."""
prefix = UDM_PROXY_PREFIX if self.is_udm else ""
return f"https://{self.host}{prefix}{path}"
def login(self): def login(self):
"""Authenticate with the Unifi Controller.""" """Authenticate with the Unifi Controller or UDM Pro."""
payload = { payload = {
"username": self.username, "username": self.username,
"password": self.password, "password": self.password,
@ -37,7 +50,7 @@ class Unifi:
"""Send a request to the Unifi API.""" """Send a request to the Unifi API."""
if data is None: if data is None:
data = {} data = {}
uri = f'https://{self.host}{path}' uri = self._get_url(path)
headers = { headers = {
"Accept": "application/json", "Accept": "application/json",
"Content-Type": "application/json; charset=utf-8", "Content-Type": "application/json; charset=utf-8",
@ -63,11 +76,35 @@ class Unifi:
except requests.RequestException as e: except requests.RequestException as e:
raise ValidationError(f"Failed to retrieve sites: {e}") raise ValidationError(f"Failed to retrieve sites: {e}")
def test_connection(self): def get_firewall_rules(self):
"""Test connection and fetch available sites.""" """Retrieve all firewall rules."""
if not self.login(): try:
raise ValidationError("Login failed. Check your credentials.") r = self.request(UNIFI_FIREWALL_RULES_PATH, method='GET')
if r.ok:
return r.json().get('data', [])
else:
raise ValidationError(f"Failed to retrieve firewall rules: {r.text}")
except requests.RequestException as e:
raise ValidationError(f"Request to fetch firewall rules failed: {e}")
sites = self.get_sites() def get_networks(self):
site_list = [site['desc'] for site in sites] """Retrieve all networks."""
return f"Connection successful! Available sites: {', '.join(site_list)}" try:
r = self.request(UNIFI_NETWORKS_PATH, method='GET')
if r.ok:
return r.json().get('data', [])
else:
raise ValidationError(f"Failed to retrieve networks: {r.text}")
except requests.RequestException as e:
raise ValidationError(f"Request to fetch networks failed: {e}")
def get_wifis(self):
"""Retrieve all WiFi configurations."""
try:
r = self.request(UNIFI_WIFIS_PATH, method='GET')
if r.ok:
return r.json().get('data', [])
else:
raise ValidationError(f"Failed to retrieve WiFi configurations: {r.text}")
except requests.RequestException as e:
raise ValidationError(f"Request to fetch WiFi configurations failed: {e}")

View file

@ -37,7 +37,9 @@
<field name="api_port"/> <field name="api_port"/>
<field name="username"/> <field name="username"/>
<field name="password" password="True"/> <field name="password" password="True"/>
<field name="controller_type"/>
<button name="action_test_connection" type="object" string="Test Connection" class="btn-primary"/> <button name="action_test_connection" type="object" string="Test Connection" class="btn-primary"/>
<button name="action_fetch_networks_and_fill_rules" type="object" string="Fetch Networks and Create Rules" class="btn-secondary"/>
</group> </group>
</sheet> </sheet>
</form> </form>