bemade-addons/portal_planning/models/planning_slot.py
2025-06-19 11:31:33 -04:00

309 lines
13 KiB
Python

# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
from odoo.tools import float_round
from odoo.exceptions import UserError
import logging
from datetime import datetime, timedelta
_logger = logging.getLogger(__name__)
import logging
_logger = logging.getLogger(__name__)
class PlanningSlot(models.Model):
"""Extension du modèle planning.slot pour les fonctionnalités du portail."""
_inherit = 'planning.slot'
# Champs pour la modification via le portail
portal_can_modify = fields.Boolean(
string='Modifiable via le portail',
default=True,
help="Si coché, l'utilisateur du portail peut modifier ce créneau"
)
portal_modified = fields.Boolean(
string='Modifié via le portail',
default=False,
copy=False,
help="Indique si le créneau a été modifié via le portail"
)
portal_modification_date = fields.Datetime(
string='Date de modification',
copy=False,
help="Date de la dernière modification via le portail"
)
portal_modification_user_id = fields.Many2one(
'res.users',
string='Modifié par',
copy=False,
help="Utilisateur ayant effectué la dernière modification"
)
portal_modification_approved = fields.Selection([
('pending', 'En attente'),
('approved', 'Approuvé'),
('rejected', 'Refusé')
], string='Statut de modification', default=False, copy=False)
portal_modification_notes = fields.Text(
string='Notes de modification',
copy=False,
help="Notes concernant la modification"
)
portal_original_start = fields.Datetime(
string='Début original',
copy=False,
help="Date de début originale avant modification"
)
portal_original_end = fields.Datetime(
string='Fin originale',
copy=False,
help="Date de fin originale avant modification"
)
portal_original_role_id = fields.Many2one(
'planning.role',
string='Rôle original',
copy=False,
help="Rôle original avant modification"
)
# Champs pour la création via le portail
portal_created = fields.Boolean(
string='Créé via le portail',
default=False,
copy=False,
help="Indique si le créneau a été créé via le portail"
)
portal_creation_date = fields.Datetime(
string='Date de création',
copy=False,
help="Date de création via le portail"
)
portal_creation_user_id = fields.Many2one(
'res.users',
string='Créé par',
copy=False,
help="Utilisateur ayant créé le créneau"
)
portal_creation_approved = fields.Selection([
('pending', 'En attente'),
('approved', 'Approuvé'),
('rejected', 'Refusé')
], string='Statut de création', default=False, copy=False)
portal_creation_notes = fields.Text(
string='Notes de création',
copy=False,
help="Notes concernant la création"
)
# Champs pour la confirmation via le portail
portal_confirmed = fields.Boolean(
string='Confirmé via le portail',
default=False,
copy=False,
help="Indique si le créneau a été confirmé via le portail"
)
portal_confirmation_date = fields.Datetime(
string='Date de confirmation',
copy=False,
help="Date de confirmation via le portail"
)
# Champs pour les timesheets générés
timesheet_ids = fields.One2many(
'account.analytic.line',
'planning_slot_id',
string='Feuilles de temps',
copy=False,
help="Feuilles de temps générées pour ce créneau"
)
# Champs calculés
portal_status = fields.Selection([
('draft', 'À confirmer'),
('confirmed', 'Confirmé'),
('modified', 'Modifié'),
('pending_approval', 'En attente d\'approbation')
], string='Statut portail', compute='_compute_portal_status', store=True)
@api.depends('state', 'portal_confirmed', 'portal_modified', 'portal_modification_approved', 'portal_created', 'portal_creation_approved')
def _compute_portal_status(self):
"""Calcule le statut du créneau pour l'affichage dans le portail."""
for slot in self:
if slot.portal_created and slot.portal_creation_approved == 'pending':
slot.portal_status = 'pending_approval'
elif slot.portal_modified and slot.portal_modification_approved == 'pending':
slot.portal_status = 'pending_approval'
elif slot.portal_confirmed:
slot.portal_status = 'confirmed'
elif slot.portal_modified:
slot.portal_status = 'modified'
else:
slot.portal_status = 'draft'
def action_confirm_portal(self):
"""Confirme le créneau via le portail."""
self.ensure_one()
if self.state != 'published':
return False
self.write({
'portal_confirmed': True,
'portal_confirmation_date': fields.Datetime.now(),
'state': 'published' # Assure que l'état reste publié
})
# Générer les timesheets si le paramètre est activé
auto_generate_timesheet = self.env['ir.config_parameter'].sudo().get_param('portal_planning.auto_generate_timesheet', True)
if auto_generate_timesheet and self.employee_id:
self._generate_timesheet()
# Envoyer une notification au responsable
if hasattr(self, 'message_post'):
self.message_post(
body=_('Créneau confirmé via le portail par %s') % (self.employee_id.name),
message_type='notification',
subtype_id=self.env.ref('mail.mt_note').id
)
return True
def action_modify_portal(self, vals):
"""Modifie le créneau via le portail."""
self.ensure_one()
if not self.portal_can_modify:
return False
# Sauvegarder les valeurs originales avant modification
original_vals = {
'portal_original_start': self.start_datetime,
'portal_original_end': self.end_datetime,
'portal_original_role_id': self.role_id.id,
'portal_modified': True,
'portal_modification_date': fields.Datetime.now(),
'portal_modification_user_id': self.env.user.id,
}
# Vérifier si la modification nécessite une approbation
auto_approve = self.env['ir.config_parameter'].sudo().get_param('portal_planning.auto_approve_modifications', False)
has_auto_approve = False
if self.employee_id and self.employee_id.resource_id and hasattr(self.employee_id.resource_id, 'portal_auto_approve_modifications'):
has_auto_approve = self.employee_id.resource_id.portal_auto_approve_modifications
if auto_approve or has_auto_approve:
original_vals['portal_modification_approved'] = 'approved'
# Mettre à jour les valeurs du créneau
update_vals = {**vals, **original_vals}
self.write(update_vals)
else:
original_vals['portal_modification_approved'] = 'pending'
# Sauvegarder les valeurs originales mais ne pas mettre à jour le créneau
self.write(original_vals)
# Créer une entrée dans le modèle de modification pour suivi
self.env['portal.planning.modification'].sudo().create({
'slot_id': self.id,
'user_id': self.env.user.id,
'date': fields.Datetime.now(),
'old_start': self.start_datetime,
'old_end': self.end_datetime,
'old_role_id': self.role_id.id,
'new_start': vals.get('start_datetime', self.start_datetime),
'new_end': vals.get('end_datetime', self.end_datetime),
'new_role_id': vals.get('role_id', self.role_id.id),
'state': 'pending',
'notes': vals.get('portal_modification_notes', '')
})
# Envoyer un message dans le chatter si le module mail est installé
if hasattr(self, 'message_post'):
self.message_post(
body=_('Créneau modifié via le portail par %s') % (self.employee_id.name),
message_type='notification',
subtype_id=self.env.ref('mail.mt_note').id
)
return True
def _generate_timesheet(self):
"""Génère les entrées timesheet pour ce créneau."""
self.ensure_one()
# Vérifier si le module timesheet est installé
if 'account.analytic.line' not in self.env:
return False
# Vérifier si l'employé a un utilisateur associé
if not hasattr(self, 'employee_id') or not self.employee_id or not self.employee_id.user_id:
return False
# Vérifier si des entrées timesheet existent déjà pour ce créneau
AnalyticLine = self.env['account.analytic.line'].sudo()
# Vérifier les champs disponibles dans le modèle account.analytic.line
analytic_fields = AnalyticLine._fields
date_field = 'date' if 'date' in analytic_fields else False
employee_field = 'employee_id' if 'employee_id' in analytic_fields else False
project_field = 'project_id' if 'project_id' in analytic_fields else False
if not date_field or not employee_field:
return False
# Construire le domaine de recherche en fonction des champs disponibles
domain = []
if date_field and hasattr(self, 'start_datetime'):
domain.append((date_field, '>=', self.start_datetime.date()))
if date_field and hasattr(self, 'end_datetime'):
domain.append((date_field, '<=', self.end_datetime.date()))
if employee_field and hasattr(self, 'employee_id'):
domain.append((employee_field, '=', self.employee_id.id))
if project_field:
domain.append((project_field, '!=', False))
existing_timesheets = AnalyticLine.search(domain) if domain else AnalyticLine
# Pour simplifier, nous créons une entrée timesheet pour la durée totale du créneau
# Une implémentation plus complexe pourrait gérer les chevauchements partiels
if not existing_timesheets:
# Créer une nouvelle entrée timesheet
timesheet_vals = {
'name': self.name or _('Créneau de planning'),
'planning_slot_id': self.id,
'portal_generated': True,
'portal_confirmation_date': self.portal_confirmation_date,
}
# Ajouter les champs disponibles
if date_field and hasattr(self, 'start_datetime'):
timesheet_vals[date_field] = self.start_datetime.date()
if 'unit_amount' in analytic_fields and hasattr(self, 'allocated_hours'):
timesheet_vals['unit_amount'] = self.allocated_hours
if employee_field and hasattr(self, 'employee_id'):
timesheet_vals[employee_field] = self.employee_id.id
if 'user_id' in analytic_fields and hasattr(self, 'employee_id') and self.employee_id.user_id:
timesheet_vals['user_id'] = self.employee_id.user_id.id
# Ajouter le projet et la tâche si disponibles
# Vérifier si le module project est installé et si les champs sont disponibles
if project_field and self.env.registry.get('project.project'):
# Vérifier si le modèle planning.slot a un champ project_id
slot_fields = self._fields
if 'project_id' in slot_fields and hasattr(self, 'project_id'):
project_id = getattr(self, 'project_id', False)
if project_id:
timesheet_vals[project_field] = project_id.id
if 'task_id' in analytic_fields and hasattr(self, 'task_id'):
task_id = getattr(self, 'task_id', False)
if task_id:
timesheet_vals['task_id'] = task_id.id
# Créer l'entrée timesheet
try:
timesheet = AnalyticLine.create(timesheet_vals)
return timesheet
except (ValueError, UserError) as e:
# Log l'erreur mais ne pas interrompre le processus
_logger.error("Erreur lors de la création de l'entrée timesheet: %s", str(e))
except Exception as e:
# Log l'erreur plus grave mais ne pas interrompre le processus
_logger.error("Erreur inattendue lors de la création de l'entrée timesheet: %s", str(e))
return False
return True