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"),
],
default="all",
help=(
"Choose whether to apply overdue warnings to all vendors or only to "
"specific vendors."
),
help="Choose whether to apply overdue warnings to all vendors or only to specific vendors.",
)
warn_supplier_specific_ids = fields.Many2many(

View file

@ -20,6 +20,36 @@ class ItchCycleProductPartner(models.Model):
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(
string="Cycle moyen (jours)",
compute="_compute_average_cycle",
@ -142,7 +172,10 @@ class ItchCycleProductPartner(models.Model):
"""
# Récupérer toutes les lignes de commandes confirmées
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)
@ -193,3 +226,23 @@ class ItchCycleProductPartner(models.Model):
'sale_order_line_ids': [(6, 0, data['sale_order_line_ids'])], # Lier ou actualiser les lignes
})
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"
)
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(
string="Prochaine Date de Suivi (Itch-Cycle Min)",
compute="_compute_itch_next_delay",
# compute="_compute_itch_next_delay",
store=True
)
@ -23,8 +30,11 @@ class ResPartner(models.Model):
Calcule la date de suivi minimale parmi tous les cycles de produits associés.
"""
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
follow_up_dates = partner.reorder_cycle_product_ids.mapped('next_follow_up_date')
if follow_up_dates:
partner.itch_next_delay = min(follow_up_dates)
else:
partner.itch_next_delay = False
def action_populate_itch_cycles(self):
"""

View file

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

View file

@ -13,15 +13,20 @@
class="btn-primary"/>
<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>
<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>
</notebook>
</field>

View file

@ -1,8 +1,6 @@
from .unifi_client import Unifi
from odoo import models, fields, api
from .unifi_client import Unifi
from odoo.exceptions import ValidationError
#from unificontrol import UnifiClient
class UnifiCtrl(models.Model):
@ -38,10 +36,26 @@ class UnifiCtrl(models.Model):
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):
"""Test connection to Unifi Controller."""
"""Test connection to Unifi Controller or UDM Pro."""
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:
client.login()
return {
@ -54,4 +68,46 @@ class UnifiCtrl(models.Model):
},
}
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
)
ctrl_id = fields.Many2one(
controller_id = fields.Many2one(
'unifi.ctrl',
string='Controller',
required=True,

View file

@ -2,24 +2,37 @@ import requests
import urllib3
from odoo.exceptions import ValidationError
# API Endpoints
UNIFI_LOGIN_PATH = '/api/auth/login'
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()
class Unifi:
def __init__(self, host, username, password):
def __init__(self, host, username, password, is_udm=False):
"""Initialize Unifi client."""
self.host = host
self.username = username
self.password = password
self.is_udm = is_udm
self.session = requests.Session()
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):
"""Authenticate with the Unifi Controller."""
"""Authenticate with the Unifi Controller or UDM Pro."""
payload = {
"username": self.username,
"password": self.password,
@ -37,7 +50,7 @@ class Unifi:
"""Send a request to the Unifi API."""
if data is None:
data = {}
uri = f'https://{self.host}{path}'
uri = self._get_url(path)
headers = {
"Accept": "application/json",
"Content-Type": "application/json; charset=utf-8",
@ -63,11 +76,35 @@ class Unifi:
except requests.RequestException as e:
raise ValidationError(f"Failed to retrieve sites: {e}")
def test_connection(self):
"""Test connection and fetch available sites."""
if not self.login():
raise ValidationError("Login failed. Check your credentials.")
def get_firewall_rules(self):
"""Retrieve all firewall rules."""
try:
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()
site_list = [site['desc'] for site in sites]
return f"Connection successful! Available sites: {', '.join(site_list)}"
def get_networks(self):
"""Retrieve all networks."""
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="username"/>
<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_fetch_networks_and_fill_rules" type="object" string="Fetch Networks and Create Rules" class="btn-secondary"/>
</group>
</sheet>
</form>