Sélectionner une révision Git
calendar.py 19,40 Kio
# © 2019 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from datetime import timedelta
import pytz
from odoo import models, fields, api
from odoo.fields import Date
from odoo.tools import pycompat
from odoo.exceptions import UserError, ValidationError
from odoo.addons.calendar.models.calendar import calendar_id2real_id
class CGScopAttendee(models.Model):
_inherit = 'calendar.attendee'
timesheet_ids = fields.One2many(
comodel_name='account.analytic.line',
inverse_name='attendee_id',
string='Timesheet linked',
copy=False)
class CGScopCalendar(models.Model):
_inherit = 'calendar.event'
@api.model
def _default_coop_id(self):
if self.env.context.get('default_res_model') == 'res.partner':
if self.env.context.get('default_res_id'):
return self.env['res.partner'].browse(
self.env.context.get('default_res_id'))
return False
def _default_ur(self):
return self.env['res.company']._ur_default_get()
partner_ids = fields.Many2many(domain=[
('user_ids', '!=', False)])
type = fields.Selection(
[('outside', 'Extérieur'),
('ur', 'UR'),
('training', 'Formation'),
('absent', 'Absence, Congés, Divers')],
string="Type de Réunion")
location = fields.Text()
coop_id = fields.Many2one(
comodel_name='res.partner',
string='Contact',
domain=[('is_company', '=', 'True')],
ondelete='set null',
default=_default_coop_id)
project_id = fields.Many2one(
comodel_name="project.project",
string="Code Activité UR",
domain=[('allow_timesheets', '=', True)])
cgscop_timesheet_code_id = fields.Many2one(
related='project_id.cgscop_timesheet_code_id',
string='Code Activité National',
store=True)
ur_financial_system_id = fields.Many2one(
comodel_name='ur.financial.system',
string='Dispositif Financier')
ur_regional_convention_id = fields.Many2one(
comodel_name='ur.regional.convention',
string='Convention Régionale')
ur_id = fields.Many2one(
'union.regionale',
string='Union Régionale',
index=True,
ondelete='restrict',
default=_default_ur)
ur_financial_system_nb = fields.Integer(
string="Nb Dispositifs Financiers",
compute="_compute_ur_system_nb")
ur_regional_convention_nb = fields.Integer(
string="Nb conventions régionales",
compute="_compute_ur_system_nb")
attendees_initial = fields.Char(
string='Initiales Participants',
compute='_compute_attendees_initial')
state = fields.Selection(
[('needsAction', 'Non répondu'),
('tentative', 'Incertain'),
('declined', 'Refusé'),
('accepted', 'Accepté')],
string='Statut',
compute='_compute_attendee_state',
help="Statut du participant",
default='needsAction')
is_attendee = fields.Boolean(
string='Est participant',
compute='_compute_is_attendee',
default=False)
is_transfered = fields.Boolean(
string='Transféré',
compute='_compute_is_transfered',
default=False)
# ------------------------------------------------------
# Compute
# ------------------------------------------------------
@api.depends('ur_id')
def _compute_ur_system_nb(self):
for event in self:
# Calcul nombre de dispositifs financiers
financial_system = event.env['ur.financial.system'].search([
('ur_id', '=', event.ur_id.id)])
event.ur_financial_system_nb = len(
financial_system)
# Calcul nombre de conventions
regional_convention = event.env['ur.regional.convention'].search([
('ur_id', '=', event.ur_id.id)])
event.ur_regional_convention_nb = len(
regional_convention)
@api.depends('partner_ids')
def _compute_attendees_initial(self):
for event in self:
initials = ''
for partner in event.partner_ids:
initials += (partner.lastname[0] + '.' + partner.firstname[0]
+ ', ')
event.attendees_initial = initials
def _compute_attendee_state(self):
for event in self:
attendee = self.env['calendar.attendee'].search([
('event_id', '=', event.id),
('partner_id', '=', self.env.user.partner_id.id)])
event.state = attendee.state
def _compute_is_attendee(self):
for event in self:
if self.env.user.partner_id in event.partner_ids:
event.is_attendee = True
else:
event.is_attendee = False
def _compute_is_transfered(self):
for event in self:
event_id = event.get_metadata()[0].get('id')
attendee = self.env['calendar.attendee'].search([
('event_id', '=', event_id),
('partner_id', '=', self.env.user.partner_id.id)])
# l'attendee a des feuilles de temps liées à la même date
if (attendee.timesheet_ids
and attendee.timesheet_ids.filtered(
lambda t: t.date == event.start.date())):
event.is_transfered = True
else:
event.is_transfered = False
# ------------------------------------------------------
# Onchange
# ------------------------------------------------------
@api.onchange('project_id')
def _onchange_project_id(self):
if self.project_id.partner_id:
self.coop_id = self.project_id.partner_id
@api.onchange('coop_id')
def _onchange_coop_id(self):
# affiche l'adresse de la coop sur le RDV
address = ''
if self.coop_id.street:
address += self.coop_id.street + '\n'
if self.coop_id.street2:
address += self.coop_id.street2 + '\n'
if self.coop_id.street3:
address += self.coop_id.street3 + '\n'
if self.coop_id.zip:
address += self.coop_id.zip + ' '
if self.coop_id.city:
address += self.coop_id.city
self.location = address
# affiche le Dispositif Financier par défaut sur le RDV
# si il n'y a pas de date limite du dispositif
# ou si la date du RDV est inférieure à la date limite du dispositif
if not self.coop_id.ur_financial_system_date or \
fields.Date.to_date(self.start) <= self.coop_id.ur_financial_system_date:
self.ur_financial_system_id = self.coop_id.ur_financial_system_id
# affiche la Convention par défaut sur le RDV
# si il n'y a pas de date limite de la convention
# ou si la date du RDV est inférieure à la date limite de la convention
if not self.coop_id.ur_regional_convention_date or \
fields.Date.to_date(self.start) <= self.coop_id.ur_regional_convention_date:
self.ur_regional_convention_id = self.coop_id.\
ur_regional_convention_id
# ------------------------------------------------------
# Contrains
# ------------------------------------------------------
@api.constrains('project_id', 'start', 'stop')
def _check_activity_code(self):
for event in self:
if event.project_id:
# Récupère les entrées en intersection
# avec la plage horaire
entries = self.search([
('start', '<', event.stop),
('stop', '>', event.start),
('user_id', '=', self.env.uid),
('project_id', '=', event.project_id.id),
('id', '!=', event.id)
])
if entries:
raise ValidationError(
"Vous ne pourvez programmer 2 évènements avec le "
"même code activité sur la même plage horaire\n"
"Evènement : %s" % str(entries.mapped('name'))[1:-1])
# ------------------------------------------------------
# Fonction boutons
# ------------------------------------------------------
@api.multi
def do_accept(self):
""" Accepte l'invitation
Modifie le statut de la table Attendees
"""
for event in self:
attendee = self.env['calendar.attendee'].search([
('event_id', '=', event.id),
('partner_id', '=', self.env.user.partner_id.id)])
attendee.state = 'accepted'
@api.multi
def do_decline(self):
""" Refuse l'invitation
Modifie le statut de la table Attendees
"""
for event in self:
attendee = self.env['calendar.attendee'].search([
('event_id', '=', event.id),
('partner_id', '=', self.env.user.partner_id.id)])
attendee.state = 'declined'
@api.multi
def do_tentative(self):
""" Incertain pour l'invitation
Modifie le statut de la table Attendees
"""
for event in self:
attendee = self.env['calendar.attendee'].search([
('event_id', '=', event.id),
('partner_id', '=', self.env.user.partner_id.id)])
attendee.state = 'tentative'
@api.multi
def create_timesheet(self):
""" Crée une ligne de temps à partir de l'entrée d'agenda
"""
partner = self.env.user.partner_id
for event in self:
event_id = event.get_metadata()[0].get('id')
if partner not in event.partner_ids:
raise UserError("Vous ne faites pas partie des participants, \
vous ne pouvez donc pas transformer cette entrée d'agenda \
en ligne de temps.")
if not event.project_id.analytic_account_id:
raise UserError("Le code activité UR doit être \
renseigné sur chaque entrée d'agenda")
else:
attendee = self.env['calendar.attendee'].search([
('event_id', '=', event_id),
('partner_id', '=', partner.id)])
# l'attendee a des feuilles de temps liées à la même date
if (attendee.timesheet_ids
and attendee.timesheet_ids.filtered(
lambda t: t.date == event.start.date())):
raise UserError("Vous avez déjà transféré cette entrée \
d'agenda : %s" % event.name)
else:
values = {
'user_id': self.env.user.id,
'project_id': event.project_id.id,
'account_id': event.project_id.analytic_account_id.id,
'ur_financial_system_id': event.ur_financial_system_id.id,
'ur_regional_convention_id': event.ur_regional_convention_id.id,
'name': event.name,
'company_id': self.env.user.company_id.id,
'partner_id': event.coop_id.id,
'event_id': event_id,
'attendee_id': attendee.id,
}
# Gestion des évènements sur toute la journée
if event.allday:
# Création d'une ligne de 8h pour chaque jour
for i in range((event.stop - event.start).days + 1):
values['date'] = event.start + timedelta(days=i)
values['unit_amount'] = 8.0
ts = self.env['account.analytic.line'].create(
values)
attendee.write({'timesheet_id': ts.id})
# Gestion des évènements sur plusieurs jours non flagués
# allday
elif (event.stop - event.start).days > 0:
user_tz = self.env.user.tz
local = pytz.timezone(user_tz)
# Pour chaque jour
for i in range((event.stop - event.start).days + 1):
day = event.start + timedelta(days=i)
# si premier jour calcul heures
if i == 0:
end_day_tz = local.localize(
day.replace(hour=18))
start_tz = fields.Datetime.context_timestamp(
record=self.env.user,
timestamp=event.start)
hours_cal = ((end_day_tz - start_tz).seconds
// 3600)
hours = hours_cal if hours_cal <= 8 else 8.0
# si dernier jour
elif i == (event.stop - event.start).days:
start_day_tz = local.localize(
day.replace(hour=8))
stop_tz = fields.Datetime.context_timestamp(
record=self.env.user,
timestamp=event.stop)
hours_cal = ((stop_tz - start_day_tz).seconds
// 3600)
hours = hours_cal if hours_cal <= 8 else 8.0
else:
hours = 8.0
values['date'] = day
values['unit_amount'] = hours
ts = self.env['account.analytic.line'].create(
values)
attendee.write({'timesheet_id': ts.id})
# Gestion des évènements classiques
else:
values['date'] = event.start
values['unit_amount'] = event.duration
ts = self.env['account.analytic.line'].create(values)
attendee.write({'timesheet_id': ts.id})
@api.multi
def duplicate_entry(self):
"""
Duplique la ligne de temps
"""
for event in self:
return {
'type': 'ir.actions.act_window',
'res_model': 'calendar.event',
'view_type': 'form',
'view_mode': 'form',
'target': 'new',
'context': {
'default_project_id': event.project_id.id,
'default_name': event.name,
'default_coop_id': event.coop_id.id,
'default_partner_ids': event.partner_ids.ids,
'default_start': event.start,
'default_duration': event.duration,
'default_stop': event.stop,
'default_allday': event.allday,
'default_type': event.type,
'default_ur_financial_system_id': event.ur_financial_system_id.id,
},
}
# ------------------------------------------------------
# Override ORM
# ------------------------------------------------------
@api.multi
def read(self, fields=None, load='_classic_read'):
""" Surcharge la fonction read de calendar pour gérer le transfert des
lignes de temps sur les virtual events.
Ajoute le calcul de la valeur du champs 'is_transfered' dans la
boucle 'for calendar_id, real_id in select'
"""
if not fields:
fields = list(self._fields)
fields2 = fields and fields[:]
EXTRAFIELDS = ('privacy', 'user_id', 'duration', 'allday', 'start',
'rrule')
for f in EXTRAFIELDS:
if fields and (f not in fields):
fields2.append(f)
select = [(x, calendar_id2real_id(x)) for x in self.ids]
real_events = self.browse([real_id for calendar_id, real_id in select])
real_data = super(CGScopCalendar, real_events).read(fields=fields2,
load=load)
real_data = dict((d['id'], d) for d in real_data)
result = []
for calendar_id, real_id in select:
if not real_data.get(real_id):
continue
res = real_data[real_id].copy()
ls = calendar_id2real_id(calendar_id, with_date=res
and res.get('duration', 0) > 0
and res.get('duration') or 1)
if not isinstance(ls, (pycompat.string_types,
pycompat.integer_types)) and len(ls) >= 2:
res['start'] = ls[1]
res['stop'] = ls[2]
if res['allday']:
res['start_date'] = ls[1]
res['stop_date'] = ls[2]
else:
res['start_datetime'] = ls[1]
res['stop_datetime'] = ls[2]
if 'display_time' in fields:
res['display_time'] = self._get_display_time(
ls[1], ls[2], res['duration'], res['allday'])
attendee = self.env['calendar.attendee'].search([
('event_id', '=', ls[0]),
('partner_id', '=', self.env.user.partner_id.id)])
# l'attendee a des feuilles de temps liées à la même date
if (attendee.timesheet_ids
and attendee.timesheet_ids.filtered(
lambda t: t.date == Date.to_date(ls[1]))):
res['is_transfered'] = True
else:
res['is_transfered'] = False
res['id'] = calendar_id
result.append(res)
for r in result:
if r['user_id']:
user_id = (type(r['user_id']) in (tuple, list)
and r['user_id'][0] or r['user_id'])
partner_id = self.env.user.partner_id.id
if user_id == (self.env.user.id
or partner_id in r.get("partner_ids", [])):
continue
if r['privacy'] == 'private':
for f in r:
recurrent_fields = self._get_recurrent_fields()
public_fields = list(set(recurrent_fields
+ ['id', 'allday', 'start',
'stop', 'display_start',
'display_stop', 'duration',
'user_id', 'state', 'interval',
'count', 'recurrent_id_date',
'rrule']))
if f not in public_fields:
if isinstance(r[f], list):
r[f] = []
else:
r[f] = False
if f == 'name':
r[f] = ('Busy')
for r in result:
for k in EXTRAFIELDS:
if (k in r) and (fields and (k not in fields)):
del r[k]
return result