Compare commits
43 Commits
old16.0
...
16-career-
@ -0,0 +1,44 @@
|
||||
# GN Career
|
||||
|
||||
Module de gestion des fiches de postes et des évolutions de carrière
|
||||
|
||||
## How to install
|
||||
|
||||
1. Clone this repository in your odoo's extra-modules folder (i.e. /mnt/extra-addons)
|
||||
2. Refresh list of Applications in UI
|
||||
3. Search for 'gn_career' and click on **Install**
|
||||
|
||||
## Changelog
|
||||
|
||||
- v16.0.0.0.11 (2024/03/19):
|
||||
- clean gn_contract.py code and create action_manage_career_button
|
||||
- v16.0.0.0.10 (2024/03/18):
|
||||
- constrains task selection in mission detail
|
||||
- v16.0.0.0.9 (2024/03/17):
|
||||
- call specific view for task detail
|
||||
- v16.0.0.0.8 (2024/03/16):
|
||||
- call specific view for tasks
|
||||
- v16.0.0.0.7 (2024/03/16):
|
||||
- call special form view for mission detail in career form
|
||||
- v16.0.0.0.6 (2024/03/14):
|
||||
- add task.detail model
|
||||
- v16.0.0.0.5 (2024/03/10):
|
||||
- create career from contract form
|
||||
- v16.0.0.0.4 (2024/03/09):
|
||||
- statusbar widget for career form
|
||||
- v16.0.0.0.3 (2024/03/09):
|
||||
- Filters on tasks
|
||||
- v16.0.0.0.2 (2024/03/08):
|
||||
- Add detail of Missions for each Career
|
||||
- v16.0.0.0.1 (2024/03/02):
|
||||
- Création du module
|
||||
|
||||
## Issues
|
||||
- [] Add menuentries and views in Analyse section for missions and tasks (cf issue #12)
|
||||
- [x] Workflow Career > Mission > Tasks needs debug
|
||||
- [] Review total_percentage constraints
|
||||
- [] Review domains and unicity for tasks and missions in models (constraints)
|
||||
- [] clean and prune methods
|
||||
- [] ensure cascade deletion
|
||||
- [] create gn_career.career.tree view
|
||||
- [] find some mechanisms to ensure career's state is updated. It is neccesary for contract's method action_manage_career.
|
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "France - Fiche de poste",
|
||||
"version": "16.0.0.0.11",
|
||||
"category": "HR",
|
||||
"summary": "Configuration de la fiche de poste et de son évolution conventionnelle",
|
||||
"author": "Le Garage Numérique",
|
||||
"maintainers": ["makayabou"],
|
||||
"website": "https://odoo.legaragenumerique.fr",
|
||||
"depends": [
|
||||
"hr",
|
||||
"hr_contract",
|
||||
"l10n_fr_oca",
|
||||
"gn_cc",
|
||||
"gn_contract",
|
||||
],
|
||||
"data": [
|
||||
"views/gn_career_mission_detail.xml",
|
||||
"views/gn_career_task_detail.xml",
|
||||
"views/gn_career.xml",
|
||||
"views/gn_contract.xml",
|
||||
"data/gn_career_menus.xml",
|
||||
"security/ir.model.access.csv",
|
||||
],
|
||||
"license": "LGPL-3",
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<menuitem
|
||||
id="gn_career.careers_menu"
|
||||
name="Fiches de poste"
|
||||
parent="hr.menu_hr_employee_payroll"
|
||||
sequence="300"
|
||||
action="gn_career.careers_configuration"
|
||||
groups="hr.group_hr_manager"/>
|
||||
<menuitem
|
||||
id="gn_career.career_configuration_menu_group"
|
||||
name="Analyse du poste"
|
||||
parent="hr.menu_human_resources_configuration"
|
||||
sequence="300"
|
||||
groups="hr.group_hr_manager"/>
|
||||
<menuitem
|
||||
id="gn_career.missions_configuration_menu"
|
||||
name="Missions"
|
||||
parent="gn_career.career_configuration_menu_group"
|
||||
sequence="100"
|
||||
action="gn_career.missions_configuration"
|
||||
groups="hr.group_hr_manager"/>
|
||||
<menuitem
|
||||
id="gn_career.tasks_configuration_menu"
|
||||
name="Tâches"
|
||||
parent="gn_career.career_configuration_menu_group"
|
||||
sequence="200"
|
||||
action="gn_career.tasks_configuration"
|
||||
groups="hr.group_hr_manager"/>
|
||||
</odoo>
|
@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import gn_contract
|
||||
from . import gn_career
|
||||
from . import gn_mission
|
@ -0,0 +1,254 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, api
|
||||
from odoo.exceptions import ValidationError
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class GnCareer(models.Model):
|
||||
_name = "gn_career.career"
|
||||
_description = "Analyse du poste"
|
||||
_order = 'start_date'
|
||||
|
||||
name = fields.Char("Nom", compute='_compute_name')
|
||||
start_date = fields.Date('From', required=True, default=lambda self: fields.Date.today())
|
||||
contract_id = fields.Many2one('hr.contract', string="Contrat ou Avenant associé", ondelete='cascade')
|
||||
employee_id = fields.Many2one(string="Employee", related='contract_id.employee_id', readonly=True, store=True)
|
||||
associated_careers_count = fields.Integer(compute='_compute_associated_careers_count', store=True)
|
||||
|
||||
state = fields.Selection([
|
||||
('draft', 'Brouillon'),
|
||||
('wait_manager_approval', "En attente de validation du manager"),
|
||||
('wait_director_approval', "En attente de validation par la direction"),
|
||||
('wait_employee_approval', "En attente de signature par le salarié"),
|
||||
('ready', 'Prête'),
|
||||
('active', 'Active'),
|
||||
], string="État", default='draft', required=True)
|
||||
|
||||
mission_ids = fields.Many2many('gn_career.mission', 'career_ids', compute='_compute_mission_ids', string="Missions effectuées", store=True)
|
||||
mission_detail_ids = fields.One2many('gn_career.mission.detail', 'career_id', string="Détail de la mission")
|
||||
|
||||
#total_percentage = fields.Float(compute='_compute_total_percentage', string="Total Percentage", store=True)
|
||||
|
||||
#@api.depends('mission_detail_ids.percentage')
|
||||
def _compute_total_percentage(self):
|
||||
_logger.warning("Enter in _compute_total_percentage")
|
||||
for record in self:
|
||||
record.total_percentage = sum(mission.percentage for mission in record.mission_detail_ids)
|
||||
|
||||
#@api.constrains('total_percentage')
|
||||
def _check_total_percentage(self):
|
||||
for record in self:
|
||||
_logger.warning("in career._check_total_percentage, 'bypass_total_percentage_check' in context: %s", self._context.get('bypass_total_percentage_check'))
|
||||
if 'bypass_total_percentage_check' in self._context:
|
||||
return
|
||||
if record.total_percentage != 100:
|
||||
raise ValidationError("Le pourcentage total des missions au sein de la fiche de poste doit atteindre 100%. Veuillez ajuster la répartition de la mission.")
|
||||
|
||||
@api.depends('mission_detail_ids.mission_id')
|
||||
def _compute_mission_ids(self):
|
||||
for record in self:
|
||||
mission_ids_set = set()
|
||||
for mission_detail in record.mission_detail_ids:
|
||||
mission_ids_set.add(mission_detail.mission_id.id)
|
||||
record.mission_ids = [(6, 0, list(mission_ids_set))]
|
||||
|
||||
@api.depends('start_date')
|
||||
def _compute_name(self):
|
||||
for record in self:
|
||||
if record.start_date:
|
||||
# Use an f-string for formatting
|
||||
record.name = f"Fiche de poste du {record.start_date}"
|
||||
else:
|
||||
# Provide a default or handle the case where start_date isn't set
|
||||
record.name = "Fiche de poste sans date"
|
||||
|
||||
def action_open_associated_careers(self):
|
||||
self.ensure_one()
|
||||
return self.contract_id.open_associated_careers()
|
||||
|
||||
@api.depends('contract_id.related_contract_ids')
|
||||
def _compute_associated_careers_count(self):
|
||||
for record in self:
|
||||
record.associated_careers_count = record.contract_id.len_related_contracts
|
||||
|
||||
def action_define_career(self):
|
||||
_logger.warning("Enter in action_define_career in career's form ( id %s)", self.id)
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Modification de la fiche de poste',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'hr.contract',
|
||||
'res_id': new_contract.id,
|
||||
'target': 'current',
|
||||
'context': {
|
||||
'default_contract_id': self.contract_id.id,
|
||||
'default_employee_id': self.employee_id.id,
|
||||
'default_start_date': self.start_date,
|
||||
},
|
||||
}
|
||||
|
||||
def action_define_mission_detail(self):
|
||||
_logger.warning("Enter in action _define_mission_detail, which sends 'bypass_total_percentage_check' in context")
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Ajouter une mission',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'gn_career.mission.detail',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_career_id': self.id,
|
||||
'default_percentage': 100,
|
||||
},
|
||||
}
|
||||
|
||||
class GnCareerMissionDetail(models.Model):
|
||||
_name = 'gn_career.mission.detail'
|
||||
_description = "Détails d'une mission pour une fiche de poste"
|
||||
|
||||
name = fields.Char(related='mission_id.name')
|
||||
career_id = fields.Many2one('gn_career.career', string="Fiche de poste")
|
||||
mission_id = fields.Many2one('gn_career.mission', string="Mission")
|
||||
exclude_mission_ids = fields.Many2many('gn_career.mission', compute='_compute_exclude_mission_ids')
|
||||
percentage = fields.Integer("Temps occupé par la mission en % de la fiche de poste", default="100")
|
||||
tasks_percentage = fields.Float(compute='_compute_tasks_percentage', string="Répartition des tâches", store=True)
|
||||
task_ids = fields.One2many('gn_career.task.detail', 'mission_detail_id', string="Tâches incluses dans la mission")
|
||||
|
||||
#related_mission_task_ids = fields.Many2many('gn_career.task', compute='_compute_related_mission_task_ids')
|
||||
lasting_task_ids = fields.Many2many('gn_career.task', compute='_compute_lasting_mission_task_ids')
|
||||
employee_id = fields.Many2one(string="Employee", related='career_id.employee_id', readonly=True)
|
||||
|
||||
# A special field used to update task_ids domain in mission.detail form
|
||||
# With context we can exclude some of the tasks:
|
||||
# - see "other tasks" of the mission in task form view
|
||||
# - select only not yet selected task_id for task_ids tree view in mission form view
|
||||
|
||||
# _compute_lasting_mission_task_ids:
|
||||
# To select tasks available in a missionDetail, ie:
|
||||
# - part of mission_id.possible_task_ids
|
||||
# - AND not already select in current MissionDetail
|
||||
# It is used in Mission Detail Form View:
|
||||
# we have a tree view for the task_detail_ids field,
|
||||
# when adding a new task_detail_ids line, we need to be
|
||||
# restricted in choice among the results of this function.
|
||||
|
||||
@api.depends('mission_id.possible_task_ids', 'task_ids')
|
||||
def _compute_lasting_mission_task_ids(self):
|
||||
for record in self:
|
||||
if record.mission_id:
|
||||
possible_tasks_ids = record.mission_id.possible_task_ids.ids
|
||||
included_tasks_ids = record.task_ids.mapped('task_id').ids
|
||||
lasting_tasks_ids = [task_id for task_id in possible_tasks_ids if task_id not in included_tasks_ids]
|
||||
record.lasting_task_ids = [(6, 0, lasting_tasks_ids)]
|
||||
|
||||
#A special field used for remove already used tasks (for task.detail tree view in mission.detail form view)
|
||||
# and also in many2many tag in task form display, using context to exclude
|
||||
# the actual task in the mission, so we have a view on "other tasks"
|
||||
# @api.depends('task_ids', 'task_ids.task_id')
|
||||
# def _compute_related_mission_task_ids(self):
|
||||
# _logger.warning("Enter in _compute_task_ids_display")
|
||||
# current_task_id = self._context.get('exclude_task_id')
|
||||
# for record in self:
|
||||
# if current_task_id:
|
||||
# record.task_ids_display = record.task_ids.filtered(lambda t: t.id != current_task_id)
|
||||
# else:
|
||||
# record.task_ids_display = record.task_ids
|
||||
|
||||
#@api.depends('task_ids', 'task_ids.percentage')
|
||||
def _compute_tasks_percentage(self):
|
||||
for record in self:
|
||||
record.tasks_percentage = sum(task.percentage for task in record.task_ids)
|
||||
|
||||
#@api.constrains('tasks_percentage')
|
||||
def _check_total_percentage(self):
|
||||
for record in self:
|
||||
if record.tasks_percentage != 100:
|
||||
raise ValidationError("Le pourcentage total des tâches au sein de la mission doit atteindre 100%. Veuillez ajuster la répartition de la mission.")
|
||||
|
||||
|
||||
@api.depends('career_id.mission_detail_ids.mission_id')
|
||||
def _compute_exclude_mission_ids(self):
|
||||
for rec in self:
|
||||
existing_mission_ids = rec.career_id.mission_detail_ids.mapped('mission_id.id')
|
||||
rec.exclude_mission_ids = [(6, 0, existing_mission_ids)]
|
||||
|
||||
# Action to open task form in mission detail view
|
||||
# it opens task_detail form if there is still tasks available in the mission
|
||||
# otherwise it opens the task creation form, associated with mission as possible_task_ids
|
||||
def check_and_open_task_detail_form(self):
|
||||
self.ensure_one()
|
||||
show_warning = False
|
||||
all_possible_tasks = self.mission_id.possible_task_ids
|
||||
included_tasks = self.task_ids.mapped('task_id')
|
||||
available_tasks_ids = all_possible_tasks.filtered(lambda t: t not in included_tasks).ids
|
||||
_logger.warning("Available tasks: %s", available_tasks_ids)
|
||||
if not available_tasks_ids:
|
||||
show_warning = True
|
||||
|
||||
if show_warning == True:
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': "Nouvelle tâche pour la mission",
|
||||
'res_model': 'gn_career.task',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': dict(self.env.context, default_possible_mission_ids=[(4, self.mission_id.id)]),
|
||||
'view_id': self.env.ref('gn_career.view_gn_career_task_form_in_mission_form').id,
|
||||
}
|
||||
else:
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': "Détail de la tâche",
|
||||
'res_model': 'gn_career.task.detail',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': dict(self.env.context, default_mission_detail_id=self.id, available_tasks_ids=available_tasks_ids),
|
||||
'view_id': self.env.ref('gn_career.gn_career_task_detail_form_view_in_mission_detail_view').id,
|
||||
}
|
||||
|
||||
return action
|
||||
|
||||
class GnCareerTaskDetail(models.Model):
|
||||
_name = 'gn_career.task.detail'
|
||||
_description = "Détails d'une tâche"
|
||||
|
||||
name = fields.Char(string="Nom", compute='_compute_task_id_name')
|
||||
mission_detail_id = fields.Many2one('gn_career.mission.detail', string="Mission de la fiche de poste", ondelete='cascade')
|
||||
career_id = fields.Many2one('gn_career.career', related='mission_detail_id.career_id', string="Fiche de poste", readonly=True)
|
||||
employee_id = fields.Many2one(string="Employee", related='career_id.employee_id', readonly=True)
|
||||
#mission_id = fields.Many2one('gn_career.mission', related='mission_detail_id.mission_id', string="")
|
||||
|
||||
task_id = fields.Many2one('gn_career.task', string="Tâche associée")
|
||||
percentage = fields.Integer("Temps occupé par la tâche en % de la mission", default="100")
|
||||
|
||||
other_tasks_in_mission = fields.Many2many('gn_career.task.detail', compute='_compute_other_tasks_in_mission')
|
||||
|
||||
@api.depends('task_id', 'percentage')
|
||||
def _compute_task_id_name(self):
|
||||
for record in self:
|
||||
task_name = record.task_id.name or "Tâche indéfinie"
|
||||
if record.percentage:
|
||||
record.name = "{} {:.2f}%".format(task_name, record.percentage)
|
||||
else:
|
||||
record.name = task_name
|
||||
|
||||
@api.depends('mission_detail_id.task_ids')
|
||||
def _compute_other_tasks_in_mission(self):
|
||||
for record in self:
|
||||
record.other_tasks_in_mission = [(6, 0, record.mission_detail_id.task_ids.filtered(lambda t: t.id != record.id ).ids)]
|
||||
|
||||
#all_possible_tasks = record.mission_detail_id.mission_id.possible_task_ids
|
||||
#included_tasks = record.mission_detail_id.task_ids.mapped('task_id')
|
||||
#record.other_tasks_in_mission = [(6, 0, all_possible_tasks.filtered(lambda t: t not in included_tasks).ids)]
|
||||
|
||||
|
||||
#A special field used to display "other tasks of the mission" in task.detail form view
|
||||
# @api.depends('mission_detail_id.task_ids', 'mission_detail_id.task_ids.task_id')
|
||||
# def _compute_other_tasks_in_mission(self):
|
||||
# current_task_id = self._context.get('exclude_task_id') or False
|
||||
# for record in self:
|
||||
# record.other_tasks_in_mission = record.mission_detail_id.task_ids.filtered(lambda t: t.task_id != current_task_id)
|
@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, api
|
||||
from datetime import datetime
|
||||
from odoo.exceptions import ValidationError
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class GnCareerContract(models.Model):
|
||||
_inherit = "hr.contract"
|
||||
|
||||
career_ids = fields.One2many("gn_career.career", 'contract_id',
|
||||
string="Analyses du poste")
|
||||
|
||||
def open_associated_careers(self):
|
||||
_logger.warning("Open Active Career called on records: %s", self)
|
||||
self.ensure_one() # Ensure that the method is called on a single record
|
||||
|
||||
associated_contract_ids = [contract.id for contract in self.related_contract_ids]
|
||||
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
"views": [[False, "tree"], [False, "form"]],
|
||||
'res_model': 'gn_career.career',
|
||||
'target': 'current',
|
||||
'domain': [('contract_id', 'in', associated_contract_ids)],
|
||||
}
|
||||
return action
|
||||
|
||||
def action_manage_career(self):
|
||||
self.ensure_one()
|
||||
if self.career_ids:
|
||||
active_careers = self.career_ids.filtered(lambda c: c.start_date <= fields.Date.today() and c.state != 'draft')
|
||||
latest_career = active_careers.sorted(key=lambda c: c.start_date, reverse=True)[:1]
|
||||
if latest_career:
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
"views": [[False, "form"]],
|
||||
'res_model': 'gn_career.career',
|
||||
'res_id': latest_career.id,
|
||||
'target': 'current',
|
||||
}
|
||||
else:
|
||||
latest_draft_career = self.career_ids.filtered(lambda c: c.state == 'draft').sorted(key=lambda c: c.start_date, reverse=True)[:1]
|
||||
if latest_draft_career:
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
"views": [[False, "form"]],
|
||||
'res_model': 'gn_career.career',
|
||||
'res_id': latest_draft_career.id,
|
||||
'target': 'current',
|
||||
}
|
||||
else:
|
||||
raise UserError("Il semble y avoir un problème avec les fiches de poste associées au contrat. Accédez au menu 'Employés > Fiches de Poste' pour accéder manuellement à la fiche de poste")
|
||||
else:
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Définir la fiche de poste',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'gn_career.career',
|
||||
'context': {
|
||||
'default_contract_id': self.id,
|
||||
'default_employee_id': self.employee_id.id,
|
||||
'default_start_date': self.date_validity_start or self.date_start,
|
||||
},
|
||||
'target': 'current',
|
||||
}
|
||||
return action
|
||||
|
||||
|
||||
|
||||
|
||||
# def action_open_career(self):
|
||||
# self.ensure_one()
|
||||
# if self.career_ids:
|
||||
# valid_careers = self.career_ids.filtered(lambda c: c.start_date and c.start_date <= fields.Date.today())
|
||||
# latest_career = valid_careers.sorted(key=lambda c: c.start_date, reverse=True)[:1]
|
||||
# if latest_career:
|
||||
# action = {
|
||||
# 'type': 'ir.actions.act_window',
|
||||
# "views": [[False, "form"]],
|
||||
# 'res_model': 'gn_career.career',
|
||||
# 'res_id': latest_career.id,
|
||||
# 'target': 'current',
|
||||
# }
|
||||
# return action
|
||||
|
||||
# def action_define_career(self):
|
||||
# _logger.warning("Enter in action_define_career for contracts'id %s", self.id)
|
||||
# self.ensure_one()
|
||||
# action = {
|
||||
# 'type': 'ir.actions.act_window',
|
||||
# 'name': 'Définir la fiche de poste',
|
||||
# 'view_mode': 'form',
|
||||
# 'res_model': 'gn_career.career',
|
||||
# 'context': {
|
||||
# 'default_contract_id': self.id,
|
||||
# 'default_employee_id': self.employee_id.id,
|
||||
# 'default_start_date': self.date_validity_start or self.date_start,
|
||||
# },
|
||||
# 'target': 'current',
|
||||
# }
|
||||
# return action
|
@ -0,0 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo 16 CE. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, api
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class GnCareerTask(models.Model):
|
||||
_name = 'gn_career.task'
|
||||
_description = "Tâches réalisées au cours de la mission"
|
||||
|
||||
name = fields.Char("Nom de la tâche")
|
||||
description = fields.Text("Description de la tâche")
|
||||
possible_mission_ids = fields.Many2many('gn_career.mission', string="Missions pouvant inclure cette tâche")
|
||||
effective_mission_ids = fields.Many2many('gn_career.mission.detail', compute='_compute_effective_mission_ids', relation="effective_missions", string="Missions définies impliquant cette tâche")
|
||||
active_mission_ids = fields.Many2many('gn_career.mission.detail', relation="active_missions", string="Missions actives incluant cette tâche")
|
||||
task_detail_ids = fields.One2many('gn_career.task.detail', 'task_id', string="Répartition des tâches au sein de la mission")
|
||||
|
||||
@api.onchange('task_detail_ids')
|
||||
def _compute_effective_mission_ids(self):
|
||||
_logger.warning("Enter in _compute_effective_mission_ids")
|
||||
for task in self:
|
||||
effective_mission_ids_set = set()
|
||||
for task_detail in task.task_detail_ids:
|
||||
if task_detail.mission_detail_id:
|
||||
effective_mission_ids_set.add(task_detail.mission_detail_id.id)
|
||||
task.effective_mission_ids = [(6, 0, list(effective_mission_ids_set))]
|
||||
|
||||
class GnCareerMission(models.Model):
|
||||
_name = 'gn_career.mission'
|
||||
_description = "Missions prévues dans le contrat"
|
||||
|
||||
name = fields.Char("Nom de la mission")
|
||||
description = fields.Text("Description de la mission")
|
||||
possible_task_ids = fields.Many2many('gn_career.task', string="Tâches pouvant être incluses dans la mission")
|
||||
|
||||
career_ids = fields.Many2many('gn_career.career', 'mission_ids', compute='_compute_career_ids', string="Fiches de poste incluant cette mission", store=True)
|
||||
mission_detail_ids = fields.One2many('gn_career.mission.detail', 'mission_id', string="Répartition des missions au sein du poste")
|
||||
|
||||
@api.depends('mission_detail_ids.career_id')
|
||||
def _compute_career_ids(self):
|
||||
_logger.warning("Enter in _compute_career_ids")
|
||||
for mission in self:
|
||||
career_ids = mission.mapped('mission_detail_ids.career_id').ids
|
||||
mission.career_ids = [(6, 0, career_ids)]
|
@ -0,0 +1,11 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_gn_career_career_user,gn_career_career_user,model_gn_career_career,base.group_user,1,0,0,0
|
||||
access_gn_career_career_admin,gn_career_career_admin,model_gn_career_career,hr_contract.group_hr_contract_manager,1,1,1,1
|
||||
access_gn_career_task_user,gn_career_task_user,model_gn_career_task,base.group_user,1,0,0,0
|
||||
access_gn_career_task_admin,gn_career_task_admin,model_gn_career_task,hr_contract.group_hr_contract_manager,1,1,1,1
|
||||
access_gn_career_mission_user,gn_career_mission_user,model_gn_career_mission,base.group_user,1,0,0,0
|
||||
access_gn_career_mission_admin,gn_career_mission_admin,model_gn_career_mission,hr_contract.group_hr_contract_manager,1,1,1,1
|
||||
access_gn_career_mission_detail_user,gn_career_mission_detail_user,model_gn_career_mission_detail,base.group_user,1,0,0,0
|
||||
access_gn_career_mission_detail_admin,gn_career_mission_detail_admin,model_gn_career_mission_detail,hr_contract.group_hr_contract_manager,1,1,1,1
|
||||
access_gn_career_task_detail_user,gn_career_task_detail_user,model_gn_career_task_detail,base.group_user,1,0,0,0
|
||||
access_gn_career_task_detail_admin,gn_career_task_detail_admin,model_gn_career_task_detail,hr_contract.group_hr_contract_manager,1,1,1,1
|
|
@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_gn_career_mission_form" model="ir.ui.view">
|
||||
<field name="name">gn_career.mission.form</field>
|
||||
<field name="model">gn_career.mission</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Mission">
|
||||
<field name="name" string="Nom"/>
|
||||
<field name="description" string="Description"/>
|
||||
<field name="possible_task_ids" string="Tâches possibles"
|
||||
context="{'tree_view_ref':'gn_career.view_gn_career_task_tree',
|
||||
'form_view_ref': 'gn_career.view_gn_career_task_form_in_mission_form'}"/>
|
||||
<field name="career_ids" string="Fiches de poste associées"/>
|
||||
<field name="mission_detail_ids"
|
||||
context="{'tree_view_ref':'gn_career.gn_career_mission_detail_tree_view'}"
|
||||
string="Organisation de la mission pour chaque employé"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_define_mission_detail_xml" model="ir.actions.act_window">
|
||||
<field name="name">Ajouter une mission</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">gn_career.mission.detail</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'default_career_id': active_id}</field>
|
||||
<field name="view_id" ref='gn_career.gn_career_mission_detail_form_in_career_view'/>
|
||||
</record>
|
||||
<record id="gn_career.missions_configuration" model="ir.actions.act_window">
|
||||
<field name="name">Missions</field>
|
||||
<field name="res_model">gn_career.mission</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
<record id="view_gn_career_task_tree" model="ir.ui.view">
|
||||
<field name="name">gn_career.task.tree</field>
|
||||
<field name="model">gn_career.task</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Tâche">
|
||||
<!-- Other fields -->
|
||||
<field name="name"/>
|
||||
<field name="description"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_gn_career_task_form" model="ir.ui.view">
|
||||
<field name="name">gn_career.task.form</field>
|
||||
<field name="model">gn_career.task</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Tâche">
|
||||
<!-- Other fields -->
|
||||
<field name="name"/>
|
||||
<field name="description"/>
|
||||
<field name="possible_mission_ids"/>
|
||||
<field name="effective_mission_ids"
|
||||
context="{'exclude_task_id': active_id, 'tree_view_ref':'gn_career.gn_career_mission_detail_tree_view'}"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_gn_career_task_form_in_mission_form" model="ir.ui.view">
|
||||
<field name="name">gn_career.task.form.mission.form</field>
|
||||
<field name="model">gn_career.task</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Tâche">
|
||||
<!-- Other fields -->
|
||||
<field name="name" string="Nom"/>
|
||||
<field name="description" string="Description"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_gn_career_career_form" model="ir.ui.view">
|
||||
<field name="name">gn_career.career.form</field>
|
||||
<field name="model">gn_career.career</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Tâche">
|
||||
<header>
|
||||
<button name="action_define_career" type="object"
|
||||
groups="hr_contract.group_hr_contract_manager"
|
||||
string="Modifier la fiche de poste" class="oe_highlight"
|
||||
attrs="{'invisible': [('state', '=', 'active')]}"/>
|
||||
<field name="state" groups="!hr_contract.group_hr_contract_manager" widget="statusbar"/>
|
||||
<field name="state" groups="hr_contract.group_hr_contract_manager" widget="statusbar" options="{'clickable': '1'}"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div name='button_box' class="oe_button_box">
|
||||
<button name="action_open_associated_careers" type='object' class="oe_stat_button" icon="fa-money" groups="hr_contract.group_hr_contract_manager">
|
||||
<field name="associated_careers_count" widget="statinfo" string="Fiche de poste"/>
|
||||
</button>
|
||||
</div>
|
||||
<group name="top_info">
|
||||
<field name="name"/>
|
||||
<field name="start_date"/>
|
||||
<field name="employee_id" readonly="1"/>
|
||||
<field name="contract_id" readonly="1"/>
|
||||
<field name="mission_ids" invisible="1"/>
|
||||
<field name="mission_detail_ids" readonly="0"
|
||||
context="{'tree_view_ref':'gn_career.gn_career_mission_detail_tree_view_in_career_view',
|
||||
'form_view_ref': 'gn_career.gn_career_mission_detail_form_in_career_view'}"/>
|
||||
|
||||
<!--options="{'no_create_edit': True, 'no_create': True,
|
||||
'views': {
|
||||
'tree': [('ref', 'gn_career.gn_career_mission_detail_tree_view_in_career_view')],
|
||||
'form': [('ref', 'gn_career.gn_career_mission_detail_form_in_career_view')]
|
||||
}}"/>-->
|
||||
<!--<field name="total_percentage"/> -->
|
||||
<button name="%(action_define_mission_detail_xml)d" type='action' class="oe_highlight"
|
||||
groups="hr_contract.group_hr_contract_manager"
|
||||
string="Ajouter une mission (xml)"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_career.tasks_configuration" model="ir.actions.act_window">
|
||||
<field name="name">Tâches</field>
|
||||
<field name="res_model">gn_career.task</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0), (0, 0, {'view_mode': 'tree', 'view_id': ref('view_gn_career_task_tree')})]"/>
|
||||
</record>
|
||||
<record id="gn_career.careers_configuration" model="ir.actions.act_window">
|
||||
<field name="name">Analyses de poste</field>
|
||||
<field name="res_model">gn_career.career</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
</odoo>
|
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="gn_career_mission_detail_form_view" model="ir.ui.view">
|
||||
<field name="name">gn_career.mission.detail.form</field>
|
||||
<field name="model">gn_career.mission.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Détail des missions">
|
||||
<sheet string="Détail de la mission">
|
||||
<group>
|
||||
<group>
|
||||
<field name="employee_id"/>
|
||||
<field name="career_id"/>
|
||||
<field name="mission_id"/>
|
||||
<field name="related_mission_task_ids" invisible="1"/>
|
||||
<field name="task_ids" domain="[('id', 'in', related_mission_task_ids)]"/>
|
||||
<field name="percentage"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_career_mission_detail_tree_view" model="ir.ui.view">
|
||||
<field name="name">gn_career.mission.detail.tree</field>
|
||||
<field name="model">gn_career.mission.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mission Details">
|
||||
<field name="mission_id"/>
|
||||
<field name="percentage"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_career_mission_detail_tree_view_in_mission_view" model="ir.ui.view">
|
||||
<field name="name">gn_career.mission.detail.tree.in_mission_view</field>
|
||||
<field name="model">gn_career.mission.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mission Details">
|
||||
<field name="employee_id"/>
|
||||
<field name="mission_id"/>
|
||||
<field name="percentage"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_career_mission_detail_tree_view_in_task_view" model="ir.ui.view">
|
||||
<field name="name">gn_career.mission.detail.tree.in_task_view</field>
|
||||
<field name="model">gn_career.mission.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mission Details">
|
||||
<field name="employee_id"/>
|
||||
<field name="mission_id"/>
|
||||
<field name="task_ids_display" widget="many2many_tags" string="Autres tâches dans la mission"/>
|
||||
<field name="percentage"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_career_mission_detail_tree_view_in_career_view" model="ir.ui.view">
|
||||
<field name="name">gn_career.mission.detail.tree.in_career_view</field>
|
||||
<field name="model">gn_career.mission.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mission Details">
|
||||
<field name="mission_id"/>
|
||||
<field name="task_ids" widget="many2many_tags" string="Tâches"/>
|
||||
<field name="percentage"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_career_mission_detail_action" model="ir.actions.act_window">
|
||||
<field name="name">Détails des missions</field>
|
||||
<field name="res_model">gn_career.mission.detail</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0), (0, 0, {'view_mode': 'tree', 'view_id': ref('gn_career_mission_detail_tree_view')}), (0, 0, {'view_mode': 'form', 'view_id': ref('gn_career_mission_detail_form_view')})]"/>
|
||||
</record>
|
||||
</odoo>
|
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="gn_career_mission_detail_form_in_career_view" model="ir.ui.view">
|
||||
<field name="name">gn_career.mission.detail.form.career</field>
|
||||
<field name="model">gn_career.mission.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Détail des missions">
|
||||
<sheet string="Détail de la mission">
|
||||
<group>
|
||||
<group>
|
||||
<field name="career_id" invisible="1"/>
|
||||
<field name="exclude_mission_ids" invisible="1"/>
|
||||
<field name="mission_id" domain="[('id', 'not in', exclude_mission_ids)]"/>
|
||||
<field name="percentage"/>
|
||||
<field name="task_ids"
|
||||
options="{'no_create_edit': True, 'no_create': True,
|
||||
'views': {
|
||||
'tree': [('ref', 'gn_career.gn_career_task_detail_tree_view_in_mission_detail_view')],
|
||||
'form': [('ref', 'gn_career.gn_career_task_detail_form_view')]
|
||||
}}"
|
||||
context="{'default_mission_detail_id': active_id}"
|
||||
widget="many2many"/>
|
||||
<button name="check_and_open_task_detail_form" type="object" string="Add Task Detail" class="oe_highlight"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_career_mission_detail_tree_view" model="ir.ui.view">
|
||||
<field name="name">gn_career.mission.detail.tree</field>
|
||||
<field name="model">gn_career.mission.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mission Details">
|
||||
<field name="employee_id"/>
|
||||
<field name="mission_id"/>
|
||||
<field name="task_ids" widget="many2many_tags" string="Tâches dans la mission"/>
|
||||
<field name="percentage"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_career_mission_detail_tree_view_in_career_view" model="ir.ui.view">
|
||||
<field name="name">gn_career.mission.detail.tree.in_career_view</field>
|
||||
<field name="model">gn_career.mission.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mission Details">
|
||||
<field name="mission_id" readonly="1"/>
|
||||
<field name="task_ids" widget="many2many_tags" string="Liste des Tâches" readonly="1"/>
|
||||
<field name="percentage" readonly="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_career_mission_detail_action" model="ir.actions.act_window">
|
||||
<field name="name">Détails des missions</field>
|
||||
<field name="res_model">gn_career.mission.detail</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0), (0, 0, {'view_mode': 'tree', 'view_id': ref('gn_career.gn_career_mission_detail_tree_view')}), (0, 0, {'view_mode': 'form', 'view_id': ref('gn_career.gn_career_mission_detail_form_in_career_view')})]"/>
|
||||
</record>
|
||||
</odoo>
|
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="gn_career_task_detail_form_view" model="ir.ui.view">
|
||||
<field name="name">gn_career.task.detail.form</field>
|
||||
<field name="model">gn_career.task.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Détail des tâches">
|
||||
<sheet string="Détail des tâches">
|
||||
<group>
|
||||
<group>
|
||||
<field name="employee_id"/>
|
||||
<field name="career_id"/>
|
||||
<field name="mission_detail_id"/>
|
||||
<field name="percentage"/>
|
||||
<field name="other_tasks_in_mission" widget="many2many_tags"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_career_task_detail_form_view_in_mission_detail_view" model="ir.ui.view">
|
||||
<field name="name">gn_career.task.detail.form.in_mission_detail_view</field>
|
||||
<field name="model">gn_career.task.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Répartition des tâches">
|
||||
<field name="other_tasks_in_mission" invisible="1"/>
|
||||
<field name="task_id" domain="[('id', 'in', context.get('available_tasks_ids', []))]"/>
|
||||
<field name="percentage"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_career_task_detail_tree_view" model="ir.ui.view">
|
||||
<field name="name">gn_career.task.detail.tree</field>
|
||||
<field name="model">gn_career.task.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Détails de la tâche">
|
||||
<field name="mission_detail_id"/>
|
||||
<field name="task_id"/>
|
||||
<field name="percentage"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_career_task_detail_tree_view_in_mission_detail_view" model="ir.ui.view">
|
||||
<field name="name">gn_career.task.detail.tree.in_mission_detail_view</field>
|
||||
<field name="model">gn_career.task.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Répartition des tâches">
|
||||
<field name="other_tasks_in_mission" invisible="1"/>
|
||||
<field name="task_id" domain="[('id', 'in', other_tasks_in_mission)]"/>
|
||||
<field name="percentage"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_career_task_detail_action" model="ir.actions.act_window">
|
||||
<field name="name">Détails des tâches</field>
|
||||
<field name="res_model">gn_career.task.detail</field>
|
||||
<field name="view_mode">form,tree</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0), (0, 0, {'view_mode': 'form', 'view_id': ref('gn_career_task_detail_form_view')}), (0, 0, {'view_mode': 'tree', 'view_id': ref('gn_career_task_detail_tree_view')})]"/>
|
||||
</record>
|
||||
</odoo>
|
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="gn_career.hr_contract" model="ir.ui.view">
|
||||
<field name="name">hr.contract.form.gncareer</field>
|
||||
<field name="model">hr.contract</field>
|
||||
<field name="priority" eval="30"/>
|
||||
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<!--<field name="career_ids" invisible="1"/>-->
|
||||
<button name="action_manage_career" type="object" class="oe_stat_button"
|
||||
icon="fa-pencil-square-o" groups="hr_contract.group_hr_contract_manager"
|
||||
string="Gérer la fiche de poste"/>
|
||||
|
||||
|
||||
<!--<button name="action_open_career" type='object' class="oe_stat_button"
|
||||
icon="fa-money" groups="hr_contract.group_hr_contract_manager"
|
||||
attrs="{'invisible': [('career_ids', '=', [])]}"
|
||||
string="Fiche de poste active">
|
||||
</button>
|
||||
<button name="action_define_career" type="object" class="oe_stat_button"
|
||||
icon="fa-pencil" groups="hr_contract.group_hr_contract_manager"
|
||||
attrs="{'invisible': [('career_ids', '!=', [])]}"
|
||||
string="Définir la fiche de poste">
|
||||
</button> -->
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
@ -0,0 +1,24 @@
|
||||
# GN CC
|
||||
|
||||
Module de gestion des conventions collectives par Le Garage Numérique.
|
||||
|
||||
## How to install
|
||||
|
||||
1. Clone this repository in your odoo's extra-modules folder (i.e. /mnt/extra-addons)
|
||||
2. Refresh list of Applications in UI
|
||||
3. Search for 'gn_cc' and click on **Install**
|
||||
|
||||
## Changelog
|
||||
|
||||
- v16.0.0.0.2 (2024/03/01):
|
||||
- Add 'l10n_fr_oca' as dependency
|
||||
- Add Readme
|
||||
- v16.0.0.0.1 (2024/02/29):
|
||||
- Creation of module
|
||||
|
||||
# Feature requests
|
||||
|
||||
- [] Add Convention Collective for "Organisme de Formation"
|
||||
- [] Add Convention Collective for "Centres Sociaux"
|
||||
- [] Add Convention Collective for "Prévention Spécialisée"
|
||||
|
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "France - Conventions Collective",
|
||||
"version": "16.0.0.0.2",
|
||||
"category": "France",
|
||||
"summary": "Configuration des accords de branche pour le France: Conventions Collectives",
|
||||
"author": "Le Garage Numérique",
|
||||
"maintainers": ["makayabou"],
|
||||
"website": "https://odoo.legaragenumerique.fr",
|
||||
"depends": [
|
||||
"hr",
|
||||
"hr_contract",
|
||||
"l10n_fr_oca",
|
||||
],
|
||||
"data": [
|
||||
"views/gn_cc_company.xml",
|
||||
"views/gn_cc_cc.xml",
|
||||
"data/gn_cc_cc.xml",
|
||||
"security/ir.model.access.csv"
|
||||
],
|
||||
"license": "LGPL-3",
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import gn_company
|
||||
from . import gn_cc
|
@ -0,0 +1,187 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, api
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class ConventionCollective(models.Model):
|
||||
_name = "gn_cc.cc"
|
||||
_description = "Convention Collective"
|
||||
_order = 'idcc, name'
|
||||
|
||||
name = fields.Char(string='Nom complet de la Convention Collective', required=True)
|
||||
associated_companies = fields.One2many('res.company', string="Sociétés utilisant cette convention", inverse_name="cc")
|
||||
active = fields.Boolean(default=True)
|
||||
idcc = fields.Char(string="IDCC", default="3442")
|
||||
groups = fields.Text(string="Groupes d'emploi")
|
||||
criterias = fields.Text(string="Critères de description des groupes d'emploi")
|
||||
point_cat = fields.Text(string="Catégories de points")
|
||||
point_values = fields.One2many('gn_cc.cc.point_value', string="Valeurs du point", inverse_name="cc")
|
||||
descriptions = fields.One2many('gn_cc.cc.group.description', string="Description de groupes associés à la convention", inverse_name="cc")
|
||||
actual_values = fields.One2many('gn_cc.cc.point_value', string="Valeurs du point en vigueur", compute='_compute_actual_values')
|
||||
actual_descriptions = fields.One2many('gn_cc.cc.group.description', compute='_compute_actual_descriptions')
|
||||
coeffs = fields.One2many('gn_cc.cc.group.coeff', string="Coefficient minimum des groupes", inverse_name="cc")
|
||||
actual_coeffs = fields.One2many('gn_cc.cc.group.coeff', compute='_compute_actual_coeffs')
|
||||
|
||||
@api.depends('point_values')
|
||||
def _compute_actual_values(self):
|
||||
for cc in self:
|
||||
# Reset the value first
|
||||
cc.actual_values = [(5,)]
|
||||
|
||||
# This dict will hold the actual point_value ID for each 'name'
|
||||
actual_records = {}
|
||||
|
||||
# create a default comparison_date so we can use a context var to call this method in other modules (payslip)
|
||||
comparison_date = self.env.context.get('comparison_date', fields.Date.today())
|
||||
|
||||
# Iterate over all point_values to find the latest but not in the future
|
||||
for pv in cc.point_values.sorted(key='start_date', reverse=True):
|
||||
# If we haven't added a record for this 'name' yet, do it
|
||||
if pv.name not in actual_records and pv.start_date <= comparison_date:
|
||||
actual_records[pv.name] = pv
|
||||
|
||||
# Now, set the actual_values field to the IDs of the actual records
|
||||
actual_ids = [rec.id for rec in actual_records.values()]
|
||||
cc.actual_values = [(6, 0, actual_ids)]
|
||||
|
||||
@api.depends('descriptions')
|
||||
def _compute_actual_descriptions(self):
|
||||
for cc in self:
|
||||
# Reset the value first
|
||||
cc.actual_descriptions = [(5,)]
|
||||
|
||||
# This dict will hold the actual point_value ID for each 'name'
|
||||
actual_records = {}
|
||||
|
||||
# create a default comparison_date so we can use a context var to call this method in other modules (payslip)
|
||||
comparison_date = self.env.context.get('comparison_date', fields.Date.today())
|
||||
|
||||
# Iterate over all point_values to find the latest but not in the future
|
||||
for desc in cc.descriptions.sorted(key='start_date', reverse=True):
|
||||
key = (desc.group, desc.criteria)
|
||||
# If we haven't added a record for this 'name' yet, do it
|
||||
|
||||
if key not in actual_records and desc.start_date <= comparison_date:
|
||||
actual_records[key] = desc
|
||||
|
||||
# Now, set the actual_values field to the IDs of the actual records
|
||||
actual_ids = [rec.id for rec in actual_records.values()]
|
||||
cc.actual_descriptions = [(6, 0, actual_ids)]
|
||||
|
||||
@api.depends('coeffs')
|
||||
def _compute_actual_coeffs(self):
|
||||
for cc in self:
|
||||
# Reset the value first
|
||||
cc.actual_coeffs = [(5,)]
|
||||
|
||||
# This dict will hold the actual point_value ID for each 'name'
|
||||
actual_records = {}
|
||||
|
||||
# create a default comparison_date so we can use a context var to call this method in other modules (payslip)
|
||||
comparison_date = self.env.context.get('comparison_date', fields.Date.today())
|
||||
|
||||
# Iterate over all point_values to find the latest but not in the future
|
||||
for coeff in cc.coeffs.sorted(key='start_date', reverse=True):
|
||||
# If we haven't added a record for this 'name' yet, do it
|
||||
if coeff.group not in actual_records and coeff.start_date <= comparison_date:
|
||||
actual_records[coeff.group] = coeff
|
||||
|
||||
# Now, set the actual_values field to the IDs of the actual records
|
||||
actual_ids = [rec.id for rec in actual_records.values()]
|
||||
cc.actual_coeffs = [(6, 0, actual_ids)]
|
||||
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
new_record = super(ConventionCollective, self).create(vals)
|
||||
self.env.user.company_id.write({'cc': new_record})
|
||||
return new_record
|
||||
|
||||
class ConventionCollectiveCoeffMin(models.Model):
|
||||
_name = "gn_cc.cc.group.coeff"
|
||||
_description = "Coefficient minimum pour le groupe d'emploi"
|
||||
_order = 'start_date'
|
||||
|
||||
@api.model
|
||||
def _default_cc(self):
|
||||
return self.env.user.company_id.cc
|
||||
|
||||
cc = fields.Many2one('gn_cc.cc', required=True, default='_default_cc', string="Convention collective associée")
|
||||
|
||||
@api.model
|
||||
def _get_groups_selection(self):
|
||||
_logger.info("Current compagnie: %s, Current CC: %s, Groups: %s", self.env.user.company_id.name, self.env.user.company_id.cc.idcc, self.env.user.company_id.cc.groups)
|
||||
if self.env.user.company_id.cc and self.env.user.company_id.cc.groups:
|
||||
groups_list = [(group.strip(), group.strip()) for group in self.env.user.company_id.cc.groups.split(';')]
|
||||
return groups_list
|
||||
else:
|
||||
return []
|
||||
|
||||
start_date = fields.Date('From', required=True, default=lambda self: fields.Date.today())
|
||||
group = fields.Selection(selection='_get_groups_selection', required=True, string="Groupe associé au coefficient")
|
||||
coeff_min = fields.Integer(string="Coefficient Minimum", required=True)
|
||||
|
||||
|
||||
class ConventionCollectiveGroupDescription(models.Model):
|
||||
_name = "gn_cc.cc.group.description"
|
||||
_description = "Descriptions des groupes d'emploi"
|
||||
_order = 'start_date'
|
||||
|
||||
@api.model
|
||||
def _default_cc(self):
|
||||
return self.env.user.company_id.cc
|
||||
|
||||
@api.model
|
||||
def _get_groups_selection(self):
|
||||
if self.env.user.company_id.cc and self.env.user.company_id.cc.groups:
|
||||
groups_list = [(group.strip(), group.strip()) for group in self.env.user.company_id.cc.groups.split(';')]
|
||||
return groups_list
|
||||
else:
|
||||
return []
|
||||
|
||||
@api.model
|
||||
def _get_criterias_selection(self):
|
||||
if self.env.user.company_id.cc and self.env.user.company_id.cc.criterias:
|
||||
criterias_list = [(criteria.strip(), criteria.strip()) for criteria in self.env.user.company_id.cc.criterias.split(';')]
|
||||
return criterias_list
|
||||
else:
|
||||
return []
|
||||
|
||||
cc = fields.Many2one('gn_cc.cc', required=True, default=_default_cc, string="Convention collective associée")
|
||||
start_date = fields.Date('From', required=True, default=lambda self: fields.Date.today())
|
||||
group = fields.Selection(selection='_get_groups_selection', required=True, string="Groupe")
|
||||
criteria = fields.Selection(selection='_get_criterias_selection', required=True, string="Critère de classification")
|
||||
description = fields.Text(required=True)
|
||||
|
||||
|
||||
class ConventionCollectivePointValue(models.Model):
|
||||
_name = "gn_cc.cc.point_value"
|
||||
_description = "Valeur du point"
|
||||
_order = 'start_date, cc'
|
||||
|
||||
@api.model
|
||||
def _default_cc(self):
|
||||
return self.env.user.company_id.cc
|
||||
|
||||
@api.model
|
||||
def _get_point_cat_selection(self):
|
||||
if self.env.user.company_id.cc and self.env.user.company_id.cc.point_cat:
|
||||
point_cat_list = [(cat.strip(), cat.strip()) for cat in self.env.user.company_id.cc.point_cat.split(';')]
|
||||
return point_cat_list
|
||||
else:
|
||||
return []
|
||||
|
||||
start_date = fields.Date('From', required=True)
|
||||
cc = fields.Many2one('gn_cc.cc', required=True, default=_default_cc, string="Convention collective associée")
|
||||
name = fields.Selection(selection='_get_point_cat_selection', required=True, string="Catégorie de point")
|
||||
value = fields.Monetary(string='Valeur du Point', help='Valeur du point défini par les partenaires sociaux', required=True)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency',
|
||||
string='Currency',
|
||||
required=True,
|
||||
default=lambda self: self.env.user.company_id.currency_id.id,
|
||||
help="Currency"
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_gn_cc_cc_user,gn_cc_cc_user,model_gn_cc_cc,base.group_user,1,0,0,0
|
||||
access_gn_cc_cc_admin,gn_cc_cc_admin,model_gn_cc_cc,hr_contract.group_hr_contract_manager,1,1,1,1
|
||||
access_gn_cc_cc_group_coeff_user,gn_cc_cc_group_coeff_user,model_gn_cc_cc_group_coeff,base.group_user,1,0,0,0
|
||||
access_gn_cc_cc_group_coeff_admin,gn_cc_cc_group_coeff_admin,model_gn_cc_cc_group_coeff,hr_contract.group_hr_contract_manager,1,1,1,1
|
||||
access_gn_cc_cc_group_description_user,gn_cc_cc_group_description_user,model_gn_cc_cc_group_description,base.group_user,1,0,0,0
|
||||
access_gn_cc_cc_group_description_admin,gn_cc_cc_group_description_admin,model_gn_cc_cc_group_description,hr_contract.group_hr_contract_manager,1,1,1,1
|
||||
access_gn_cc_cc_point_value_user,gn_cc_cc_point_value_user,model_gn_cc_cc_point_value,base.group_user,1,0,0,0
|
||||
access_gn_cc_cc_point_value_admin,gn_cc_cc_point_value_admin,model_gn_cc_cc_point_value,hr_contract.group_hr_contract_manager,1,1,1,1
|
|
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="gn_cc_form_view" model="ir.ui.view">
|
||||
<field name="name">gn_cc.cc.form</field>
|
||||
<field name="model">gn_cc.cc</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Conventions Collectives">
|
||||
<sheet string="Convention Collective">
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="Nom complet de la convention collective" />
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="idcc" />
|
||||
<field name="groups" placeholder="Liste des groupes, séparés par un point-virgule"/>
|
||||
<field name="criterias" placeholder="Liste des critères de classement des groupes, séparés par un retour à la ligne"/>
|
||||
<field name="point_cat" placeholder="Liste des catégories de points, séparés par un retour à la ligne"/>
|
||||
|
||||
<field name="actual_values" context="{'default_cc': active_id}" options="{'create': True, 'delete': True}">
|
||||
<tree editable="bottom">
|
||||
<field name="name" help="catégorie de point"/>
|
||||
<field name="start_date"/>
|
||||
<field name="value"/>
|
||||
</tree>
|
||||
</field>
|
||||
<field name="actual_descriptions" context="{'default_cc': active_id}" widget="one2many_list" options="{'create': True, 'delete': True}">
|
||||
<tree editable="bottom">
|
||||
<field name="start_date"/>
|
||||
<field name="group"/>
|
||||
<field name="criteria"/>
|
||||
<field name="description"/>
|
||||
</tree>
|
||||
</field>
|
||||
<field name="actual_coeffs" context="{'default_cc': active_id}" widget="one2many_list" options="{'create': True, 'delete': True}">
|
||||
<tree editable="bottom">
|
||||
<field name="start_date"/>
|
||||
<field name="group"/>
|
||||
<field name="coeff_min"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_cc.cc_configuration" model="ir.actions.act_window">
|
||||
<field name="name">Conventions Collectives</field>
|
||||
<field name="res_model">gn_cc.cc</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
<record id="gn_cc.cc_group_coeff_configuration" model="ir.actions.act_window">
|
||||
<field name="name">Coefficient minimum</field>
|
||||
<field name="res_model">gn_cc.cc.group.coeff</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
<record id="gn_cc.cc_group_description_configuration" model="ir.actions.act_window">
|
||||
<field name="name">Description du groupe</field>
|
||||
<field name="res_model">gn_cc.cc.group.description</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
<record id="gn_cc.cc_point_value_configuration" model="ir.actions.act_window">
|
||||
<field name="name">Valeur du point</field>
|
||||
<field name="res_model">gn_cc.cc.point_value</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
</odoo>
|
@ -0,0 +1,37 @@
|
||||
# GN Avenants
|
||||
|
||||
Module de gestion des avenants aux contrats
|
||||
|
||||
## How to install
|
||||
|
||||
1. Clone this repository in your odoo's extra-modules folder (i.e. /mnt/extra-addons)
|
||||
2. Refresh list of Applications in UI
|
||||
3. Search for 'gn_holidays' and click on **Install**
|
||||
|
||||
## Changelog
|
||||
|
||||
- v16.0.0.0.5 (2024/03/19):
|
||||
- Remove next_contract_id and related_contract_ids as it is a nightmare to recompute and avid possible infinite loop. We already have contract's history view per employee.
|
||||
- v16.0.0.0.4 (2024/03/13):
|
||||
- Create Action for Fixed-Term contract renewal
|
||||
- Readonly attribute for date_end field in contract
|
||||
- v16.0.0.0.3 (2024/03/12):
|
||||
- Liens entre contrats liés passés et futurs
|
||||
- model method and Cron job for contract status automation (!! NOT TESTED !!)
|
||||
- Add date_validity_start and date_validity_end to keep date_start and date_end from contract and keep track of child contracts (avenants) date ranges.
|
||||
- Add link to previous and next contract
|
||||
- v16.0.0.0.2 (2024/03/10):
|
||||
- Bouton de création d'un avenant
|
||||
- Vues des contrats précédents
|
||||
- v16.0.0.0.1 (2024/03/02):
|
||||
- Création du module
|
||||
|
||||
## Issues
|
||||
- [x] Needs a View for Previous contract (cf issue #9)
|
||||
- [x] Needs an Action to Create a New Contract linked with a Previous one (cf issue #10)
|
||||
- [x] Creating a New Contract (Avenant) should lead to changements to parent_contract
|
||||
- [x] associated_contracts should be in the past, and also in the future, so recomputed automatically
|
||||
- [x] Manage contract's status automatically
|
||||
- [] No CDI should have a date_end
|
||||
- [] ensure date_validity_start and date_validity_end are inside date_start and date_end
|
||||
- [] failing logic for contracts concurential dates and states
|
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Gestion des Contrats: Typologie et Gestion des Avenants",
|
||||
"version": "16.0.0.0.5",
|
||||
"category": "HR",
|
||||
"summary": "Permet de relier entre eux les contrats",
|
||||
"author": "Le Garage Numérique",
|
||||
"maintainers": ["makayabou"],
|
||||
"website": "https://odoo.legaragenumerique.fr",
|
||||
"depends": [
|
||||
"hr",
|
||||
"hr_contract",
|
||||
],
|
||||
"data": [
|
||||
"data/gn_contract.xml",
|
||||
"data/gn_contract_cron.xml",
|
||||
"views/gn_contract.xml",
|
||||
],
|
||||
"license": "LGPL-3",
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<delete model="hr.payroll.structure.type" id="hr_contract.structure_type_employee"/>
|
||||
<delete model="hr.payroll.structure.type" id="hr_contract.structure_type_worker"/>
|
||||
<record id="gn_contract_structure_type_employe" model="hr.payroll.structure.type">
|
||||
<field name="name">Employé(e)</field>
|
||||
<field name="country_id" eval="False"/>
|
||||
</record>
|
||||
<record id="gn_contract_structure_type_cadre" model="hr.payroll.structure.type">
|
||||
<field name="name">Cadre</field>
|
||||
<field name="country_id" eval="False"/>
|
||||
</record>
|
||||
<record id="gn_contract_structure_type_service_civique" model="hr.payroll.structure.type">
|
||||
<field name="name">Volontaire en Service civique</field>
|
||||
<field name="country_id" eval="False"/>
|
||||
</record>
|
||||
<record id="gn_contract_structure_type_intern" model="hr.payroll.structure.type">
|
||||
<field name="name">Stagiaire</field>
|
||||
<field name="country_id" eval="False"/>
|
||||
</record>
|
||||
<record id="gn_contract_structure_type_benevolent" model="hr.payroll.structure.type">
|
||||
<field name="name">Administrateur(-trice) bénévole</field>
|
||||
<field name="country_id" eval="False"/>
|
||||
</record>
|
||||
<record id="gn_contract_contract_cae" model="hr.contract.type">
|
||||
<field name="name">PEC - CAE</field>
|
||||
</record>
|
||||
<record id="gn_contract_contract_apprentissage" model="hr.contract.type">
|
||||
<field name="name">Contrat d'apprentissage</field>
|
||||
</record>
|
||||
<record id="gn_contract_contract_cdd" model="hr.contract.type">
|
||||
<field name="name">CDD</field>
|
||||
</record>
|
||||
<record id="gn_contract_contract_cdi" model="hr.contract.type">
|
||||
<field name="name">CDI</field>
|
||||
</record>
|
||||
<record id="gn_contract_contract_service_civique" model="hr.contract.type">
|
||||
<field name="name">Service civique</field>
|
||||
</record>
|
||||
<record id="gn_contract_contract_admin" model="hr.contract.type">
|
||||
<field name="name">Administrateur bénévole</field>
|
||||
</record>
|
||||
<record id="gn_contract_contract_internship" model="hr.contract.type">
|
||||
<field name="name">Stage</field>
|
||||
</record>
|
||||
</odoo>
|
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="cron_update_contract_status" model="ir.cron">
|
||||
<field name="name">Update Contract Status</field>
|
||||
<field name="model_id" ref="model_hr_contract"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.cron_update_contract_status()</field>
|
||||
<field name="active" eval="True"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="nextcall" eval="(datetime.now() + timedelta(hours=1)).strftime('%Y-%m-%d 00:10:00')"/>
|
||||
<field name="doall" eval="False"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import gn_contract
|
||||
from . import gn_employee
|
@ -0,0 +1,115 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import timedelta
|
||||
from odoo import fields, models, api
|
||||
from pytz import timezone
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class GnHrContract(models.Model):
|
||||
_inherit = "hr.contract"
|
||||
|
||||
previous_contract_id = fields.Many2one("hr.contract", string="Contrat précédent")
|
||||
date_validity_start = fields.Date(string="Date de prise en compte de l'avenant")
|
||||
date_validity_end = fields.Date(string="Fin de prise en compte de l'avenant")
|
||||
lineage_sequence_number = fields.Integer(string="Numéro de séquence dans l'historique du salarié", compute='_compute_lineage_sequence_number', store=True)
|
||||
|
||||
@api.depends('previous_contract_id')
|
||||
def _compute_lineage_sequence_number(self):
|
||||
for contract in self:
|
||||
if contract.previous_contract_id:
|
||||
previous_contract = contract.previous_contract_id
|
||||
previous_number = previous_contract.lineage_sequence_number
|
||||
contract.lineage_sequence_number = previous_number + 1
|
||||
else:
|
||||
contract.lineage_sequence_number = 0
|
||||
|
||||
@api.onchange('date_validity_end')
|
||||
def update_state(self):
|
||||
for record in self:
|
||||
if record.date_validity_end <= fields.Date.context_today(self):
|
||||
record.state = 'cancel'
|
||||
|
||||
def create_child_contract(self):
|
||||
self.ensure_one()
|
||||
default_vals = {
|
||||
'name': "Avenant au contrat " + self.name,
|
||||
'employee_id': self.employee_id.id,
|
||||
'structure_type_id': self.structure_type_id.id,
|
||||
'previous_contract_id': self.id,
|
||||
'date_start': self.date_start,
|
||||
'date_end': self.date_end,
|
||||
'date_validity_start': fields.Date.context_today(self),
|
||||
'date_validity_end': self.date_end,
|
||||
'resource_calendar_id': self.resource_calendar_id.id,
|
||||
'department_id': self.department_id.id,
|
||||
'job_id': self.job_id.id,
|
||||
'contract_type_id': self.contract_type_id.id,
|
||||
'hr_responsible_id': self.hr_responsible_id.id,
|
||||
'wage': self.wage,
|
||||
'notes': self.notes,
|
||||
'schedule_pay': self.schedule_pay,
|
||||
'struct_id': self.struct_id.id,
|
||||
}
|
||||
new_contract = self.create(default_vals)
|
||||
self.date_validity_end = fields.Date.from_string(default_vals['date_validity_start']) - timedelta(days=1)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Avenant',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'hr.contract',
|
||||
'res_id': new_contract.id,
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
def action_renew_contract(self):
|
||||
self.ensure_one()
|
||||
date_start = fields.Date.from_string(self.date_end) + timedelta(days=1)
|
||||
duration = fields.Date.from_string(self.date_end) - fields.Date.from_string(self.date_start)
|
||||
date_end = date_start + duration
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Renouveller le contrat',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'hr.contract',
|
||||
'context': {
|
||||
'default_name': "Renouvellement du contrat " + self.name,
|
||||
'default_employee_id': self.employee_id.id,
|
||||
'default_structure_type_id': self.structure_type_id.id,
|
||||
'default_previous_contract_id': self.id,
|
||||
'default_date_start': date_start,
|
||||
'default_date_end': date_end,
|
||||
'default_date_validity_start': date_start,
|
||||
'default_date_validity_end': date_end,
|
||||
'default_resource_calendar_id': self.resource_calendar_id.id,
|
||||
'default_department_id': self.department_id.id,
|
||||
'default_job_id': self.job_id.id,
|
||||
'default_contract_type_id': self.contract_type_id.id,
|
||||
'default_hr_responsible_id': self.hr_responsible_id.id,
|
||||
'default_wage': self.wage,
|
||||
'default_notes': self.notes,
|
||||
'default_schedule_pay': self.schedule_pay,
|
||||
'default_struct_id': self.struct_id.id,
|
||||
},
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
|
||||
def cron_update_contract_status(self):
|
||||
"""Scheduled action to update contract statuses based on start and end dates."""
|
||||
today = fields.Date.context_today(self)
|
||||
contracts_to_close = self.search([('date_validity_end', '<=', today), ('status', '=', 'open')])
|
||||
for contract in contracts_to_close:
|
||||
contract.ensure_one()
|
||||
contract.write({'status': 'cancel'})
|
||||
contracts_to_open = self.search([('date_start', '<=', today), ('date_validity_start', '<=', today), ('status', '=', 'draft'), '|', ('date_validity_end', '>', today), ('date_validity_end', '=', False)])
|
||||
for contract in contracts_to_open:
|
||||
contract.ensure_one()
|
||||
contract.write({'status': 'open'})
|
||||
if contract.previous_contract_id:
|
||||
contract.previous_contract_id.write({'status': 'close',
|
||||
'date_validity_end': contract.date_validity_start - timedelta(days=1)
|
||||
})
|
@ -0,0 +1,27 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
class GnHrEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
employee_type = fields.Selection([
|
||||
('employee', 'Employé'),
|
||||
('student', 'Étudiant'),
|
||||
('trainee', 'Stagiaire'),
|
||||
('volunteer', 'Volontaire'),
|
||||
('benevolent', 'Bénévole'),
|
||||
], string='Employee Type', default='employee', required=True,
|
||||
help="The employee type. Although the primary purpose may seem to categorize employees, this field has also an impact in the Contract History. Only Employee type is supposed to be under contract and will have a Contract History.")
|
||||
|
||||
anciennete_start_date = fields.Date(compute='_compute_anciennete_start_date', groups='hr_group.hr_user', store=True)
|
||||
|
||||
@api.depends('contract_ids.state', 'contract_ids.date_start')
|
||||
def _compute_anciennete_start_date(self):
|
||||
for employee in self:
|
||||
if employee.contract_id:
|
||||
previous_contract = employee.contract_id
|
||||
while previous_contract.previous_contract_id:
|
||||
previous_contract = previous_contract.previous_contract_id
|
||||
|
||||
employee.anciennete_start_date = previous_contract.date_start
|
||||
|
||||
else:
|
||||
employee.anciennete_start_date = False
|
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="gn_contract.hr_contract_avenant" model="ir.ui.view">
|
||||
<field name="name">hr.contract.form.avenant</field>
|
||||
<field name="model">hr.contract</field>
|
||||
<field name="priority" eval="30"/>
|
||||
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='date_start']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='date_start']" position="after">
|
||||
<field name="previous_contract_id" attrs="{'readonly': True, 'invisible': [('previous_contract_id', '=', False)]}"/>
|
||||
<field name="date_start" attrs="{'readonly': [('previous_contract_id', '!=', False)]}"/>
|
||||
<field name="date_validity_start" attrs="{'invisible': [('previous_contract_id', '=', False)]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='date_end']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='date_end']" position="after">
|
||||
<field name="date_end" attrs="{'readonly': [('previous_contract_id', '!=', False), ('date_end', '=', False)]}"/>
|
||||
<field name="date_validity_end" attrs="{'invisible': [('previous_contract_id', '=', False)]}"/>
|
||||
</xpath>
|
||||
<header>
|
||||
<button name="create_child_contract" type="object" string="Créer un avenant" class="oe_highlight"/>
|
||||
<button name="action_renew_contract" type="object" string="Renouveller un CDD" class="oe_highlight" attrs="{'invisible': [('date_end', '=', False)]}"/>
|
||||
|
||||
|
||||
</header>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
@ -0,0 +1,19 @@
|
||||
# GN Holidays
|
||||
|
||||
Module de gestion des congés et absences des employés
|
||||
|
||||
## How to install
|
||||
|
||||
1. Clone this repository in your odoo's extra-modules folder (i.e. /mnt/extra-addons)
|
||||
2. Refresh list of Applications in UI
|
||||
3. Search for 'gn_holidays' and click on **Install**
|
||||
|
||||
## Changelog
|
||||
|
||||
- v16.0.0.0.1 (2024/03/01):
|
||||
- Création du module
|
||||
- Ajouts des jours feriés pour 2024
|
||||
- Ajout des types d'absence
|
||||
|
||||
## Issues
|
||||
- [] Need prevention mechanism when CP or Compensatory Day is asked on a Public Holiday (cf issue #8)
|
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "France - Congés",
|
||||
"version": "16.0.0.0.1",
|
||||
"category": "HR",
|
||||
"summary": "Configuration des congés et absences",
|
||||
"author": "Le Garage Numérique",
|
||||
"maintainers": ["makayabou"],
|
||||
"website": "https://odoo.legaragenumerique.fr",
|
||||
"depends": [
|
||||
"hr",
|
||||
"hr_contract",
|
||||
"l10n_fr_oca",
|
||||
"hr_work_entry",
|
||||
"hr_work_entry_holidays",
|
||||
"hr_holidays_public",
|
||||
],
|
||||
"data": [
|
||||
"data/gn_holidays_public.xml",
|
||||
"data/gn_holidays.xml",
|
||||
],
|
||||
"license": "LGPL-3",
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
<!-- Work Entry Type -->
|
||||
<record id="hr_work_entry_contract.work_entry_type_compensatory" model="hr.work.entry.type">
|
||||
<field name="display_name">Congé compensatoire (récup')</field>
|
||||
<field name="code">RTT</field>
|
||||
<field name="color">15</field>
|
||||
<field name="is_leave" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_work_entry_contract.work_entry_type_home_working" model="hr.work.entry.type">
|
||||
<field name="name">Télé-travail</field>
|
||||
<field name="code">TT</field>
|
||||
<field name="color">1</field>
|
||||
<field name="is_leave" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_work_entry_contract.work_entry_type_unpaid_leave" model="hr.work.entry.type">
|
||||
<field name="name">Absence non-rémunérée</field>
|
||||
<field name="color">2</field>
|
||||
<field name="code">ABS</field>
|
||||
<field name="is_leave" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_work_entry_contract.work_entry_type_sick_leave" model="hr.work.entry.type">
|
||||
<field name="name">Congé Maladie</field>
|
||||
<field name="code">AM</field>
|
||||
<field name="is_leave" eval="True"/>
|
||||
<field name="color">9</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_work_entry_contract.work_entry_type_legal_leave" model="hr.work.entry.type">
|
||||
<field name="name">Congé payé</field>
|
||||
<field name="code">CP</field>
|
||||
<field name="is_leave" eval="True"/>
|
||||
<field name="color">6</field>
|
||||
</record>
|
||||
<!--Congés payés-->
|
||||
<record id="hr_holidays.holiday_status_cl" model="hr.leave.type">
|
||||
<field name="name">Paid Time Off</field>
|
||||
<field name="requires_allocation">yes</field>
|
||||
<field name="employee_requests">no</field>
|
||||
<field name="leave_validation_type">hr</field>
|
||||
<field name="allocation_validation_type">officer</field>
|
||||
<field name="leave_notif_subtype_id" ref="hr_holidays.mt_leave"/>
|
||||
<field name="allocation_notif_subtype_id" ref="hr_holidays.mt_leave_allocation"/>
|
||||
<field name="responsible_id" ref="base.user_admin"/>
|
||||
<field name="request_unit">day</field>
|
||||
<field name="support_document" eval="False"/>
|
||||
<field name="time_type">leave</field>
|
||||
<field name="exclude_public_holidays" eval="True"/>
|
||||
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_legal_leave"/>
|
||||
<field name="icon_id" ref="hr_holidays.icon_14"/>
|
||||
<field name="color">2</field>
|
||||
<field name="company_id" eval="False"/> <!-- Explicitely set to False for it to be available to all companies -->
|
||||
<field name="sequence">1</field>
|
||||
</record>
|
||||
|
||||
<!-- Arrêt maladie -->
|
||||
<record id="hr_holidays.holiday_status_sl" model="hr.leave.type">
|
||||
<field name="name">Sick Time Off</field>
|
||||
<field name="requires_allocation">no</field>
|
||||
<field name="color_name">red</field>
|
||||
<field name="leave_validation_type">no_validation</field>
|
||||
<field name="leave_notif_subtype_id" ref="hr_holidays.mt_leave_sick"/>
|
||||
<field name="responsible_id" ref="base.user_admin"/>
|
||||
<field name="request_unit">day</field>
|
||||
<field name="support_document" eval="True"/>
|
||||
<field name="time_type">leave</field>
|
||||
<field name="exclude_public_holidays" eval="False"/>
|
||||
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_sick_leave"/>
|
||||
<field name="icon_id" ref="hr_holidays.icon_22"/>
|
||||
<field name="color">3</field>
|
||||
<field name="company_id" eval="False"/> <!-- Explicitely set to False for it to be available to all companies -->
|
||||
<field name="sequence">2</field>
|
||||
</record>
|
||||
|
||||
<!-- Compensatory Days -->
|
||||
<record id="hr_holidays.holiday_status_comp" model="hr.leave.type">
|
||||
<field name="name">Compensatory Days</field>
|
||||
<field name="requires_allocation">yes</field>
|
||||
<field name="employee_requests">yes</field>
|
||||
<field name="leave_validation_type">hr</field>
|
||||
<field name="allocation_validation_type">officer</field>
|
||||
<field name="request_unit">hour</field>
|
||||
<field name="support_document" eval="True"/>
|
||||
<field name="time_type">leave</field>
|
||||
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_compensatory"/>
|
||||
<field name="exclude_public_holidays" eval="True"/>
|
||||
<field name="leave_notif_subtype_id" ref="hr_holidays.mt_leave"/>
|
||||
<field name="responsible_id" ref="base.user_admin"/>
|
||||
<field name="icon_id" ref="hr_holidays.icon_4"/>
|
||||
<field name="color">4</field>
|
||||
<field name="company_id" eval="False"/> <!-- Explicitely set to False for it to be available to all companies -->
|
||||
<field name="sequence">4</field>
|
||||
</record>
|
||||
|
||||
<!--Absence non-rémunérée -->
|
||||
<record id="hr_holidays.holiday_status_unpaid" model="hr.leave.type">
|
||||
<field name="name">Unpaid</field>
|
||||
<field name="requires_allocation">no</field>
|
||||
<field name="leave_validation_type">hr</field>
|
||||
<field name="allocation_validation_type">officer</field>
|
||||
<field name="request_unit">hour</field>
|
||||
<field name="unpaid" eval="True"/>
|
||||
<field name="time_type">leave</field>
|
||||
<field name="support_document" eval="True"/>
|
||||
<field name="exclude_public_holidays" eval="True"/>
|
||||
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_unpaid_leave"/>
|
||||
<field name="leave_notif_subtype_id" ref="hr_holidays.mt_leave_unpaid"/>
|
||||
<field name="responsible_id" ref="base.user_admin"/>
|
||||
<field name="icon_id" ref="hr_holidays.icon_28"/>
|
||||
<field name="color">5</field>
|
||||
<field name="company_id" eval="False"/> <!-- Explicitely set to False for it to be available to all companies -->
|
||||
<field name="sequence">3</field>
|
||||
</record>
|
||||
|
||||
<!-- Plan de cumul des congés payés-->
|
||||
<record id="cumul_cp" model="hr.leave.accrual.plan">
|
||||
<field name="name">Cumul des congés payés</field>
|
||||
<field name="time_off_type_id" ref="hr_holidays.holiday_status_cl"/>
|
||||
</record>
|
||||
|
||||
<!-- Régle de cumul des Congés payés-->
|
||||
<record id="cumul_standard" model="hr.leave.accrual.level">
|
||||
<field name="start_count">0</field>
|
||||
<field name="start_type">month</field>
|
||||
<field name="is_based_on_worked_time" eval="True"/>
|
||||
<field name="added_value">2.5</field>
|
||||
<field name="added_value_type">days</field>
|
||||
<field name="frequency">monthly</field>
|
||||
<field name="maximum_leave">90</field>
|
||||
<field name="action_with_unused_accruals">postponed</field>
|
||||
<field name="postpone_max_days">60</field>
|
||||
<field name="accrual_plan_id" ref="gn_holidays.cumul_cp"/>
|
||||
</record>
|
||||
</data>
|
||||
<delete model="hr.work.entry.type" id="hr_work_entry_contract.work_entry_type_leave"/>
|
||||
</odoo>
|
@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
<!-- Jour fériés 2024 -->
|
||||
<record id="jours_feries_2024" model="hr.holidays.public">
|
||||
<field name="year">2024</field>
|
||||
<field name="country_id" ref="base.fr"/>
|
||||
</record>
|
||||
<record id="jour_ferie_jour_de_l_an" model="hr.holidays.public.line">
|
||||
<field name="name">Jour de l'an</field>
|
||||
<field name="date" eval="'2024-01-01'"/>
|
||||
<field name="variable_date" eval="False"/>
|
||||
<field name="year_id" ref="jours_feries_2024"/>
|
||||
</record>
|
||||
<record id="jour_ferie_lundi_de_paques" model="hr.holidays.public.line">
|
||||
<field name="name">Lundi de Pâques</field>
|
||||
<field name="date" eval="'2024-04-01'"/>
|
||||
<field name="variable_date" eval="True"/>
|
||||
<field name="year_id" ref="jours_feries_2024"/>
|
||||
</record>
|
||||
<record id="jour_ferie_fete_du_travail" model="hr.holidays.public.line">
|
||||
<field name="name">Fête du Travail</field>
|
||||
<field name="date" eval="'2024-05-01'"/>
|
||||
<field name="variable_date" eval="False"/>
|
||||
<field name="year_id" ref="jours_feries_2024"/>
|
||||
</record>
|
||||
<record id="jour_ferie_victoire_1945" model="hr.holidays.public.line">
|
||||
<field name="name">Victoire 1945</field>
|
||||
<field name="date" eval="'2024-05-08'"/>
|
||||
<field name="variable_date" eval="False"/>
|
||||
<field name="year_id" ref="jours_feries_2024"/>
|
||||
</record>
|
||||
<record id="jour_ferie_ascension" model="hr.holidays.public.line">
|
||||
<field name="name">Ascension</field>
|
||||
<field name="date" eval="'2024-05-09'"/>
|
||||
<field name="variable_date" eval="True"/>
|
||||
<field name="year_id" ref="jours_feries_2024"/>
|
||||
</record>
|
||||
<record id="jour_ferie_pentecote" model="hr.holidays.public.line">
|
||||
<field name="name">Pentecôte</field>
|
||||
<field name="date" eval="'2024-05-20'"/>
|
||||
<field name="variable_date" eval="True"/>
|
||||
<field name="year_id" ref="jours_feries_2024"/>
|
||||
</record>
|
||||
<record id="jour_ferie_fete_nationale" model="hr.holidays.public.line">
|
||||
<field name="name">Fête Nationale</field>
|
||||
<field name="date" eval="'2024-07-14'"/>
|
||||
<field name="variable_date" eval="False"/>
|
||||
<field name="year_id" ref="jours_feries_2024"/>
|
||||
</record>
|
||||
<record id="jour_ferie_assomption" model="hr.holidays.public.line">
|
||||
<field name="name">Assomption</field>
|
||||
<field name="date" eval="'2024-08-15'"/>
|
||||
<field name="variable_date" eval="False"/>
|
||||
<field name="year_id" ref="jours_feries_2024"/>
|
||||
</record>
|
||||
<record id="jour_ferie_toussaint" model="hr.holidays.public.line">
|
||||
<field name="name">Toussaint</field>
|
||||
<field name="date" eval="'2024-11-01'"/>
|
||||
<field name="variable_date" eval="False"/>
|
||||
<field name="year_id" ref="jours_feries_2024"/>
|
||||
</record>
|
||||
<record id="jour_ferie_armistrice_1918" model="hr.holidays.public.line">
|
||||
<field name="name">Armistrice 1918</field>
|
||||
<field name="date" eval="'2024-11-11'"/>
|
||||
<field name="variable_date" eval="False"/>
|
||||
<field name="year_id" ref="jours_feries_2024"/>
|
||||
</record>
|
||||
<record id="jour_ferie_noel" model="hr.holidays.public.line">
|
||||
<field name="name">Jour de Noël</field>
|
||||
<field name="date" eval="'2024-12-25'"/>
|
||||
<field name="variable_date" eval="False"/>
|
||||
<field name="year_id" ref="jours_feries_2024"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="gn_payroll.cc_1418" model="gn_payroll.cc">
|
||||
<field name="name">Convention Eclat</field>
|
||||
<field name="idcc">1418</field>
|
||||
</record>
|
||||
</odoo>
|
@ -1,46 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<delete model="hr.payroll.structure.type" id="hr_contract.structure_type_employee"/>
|
||||
<delete model="hr.payroll.structure.type" id="hr_contract.structure_type_worker"/>
|
||||
<record id="gn_payroll_structure_type_employe" model="hr.payroll.structure.type">
|
||||
<field name="name">Employé(e)</field>
|
||||
<field name="country_id" eval="False"/>
|
||||
</record>
|
||||
<record id="gn_payroll_structure_type_cadre" model="hr.payroll.structure.type">
|
||||
<field name="name">Cadre</field>
|
||||
<field name="country_id" eval="False"/>
|
||||
</record>
|
||||
<record id="gn_payroll_structure_type_service_civique" model="hr.payroll.structure.type">
|
||||
<field name="name">Volontaire en Service civique</field>
|
||||
<field name="country_id" eval="False"/>
|
||||
</record>
|
||||
<record id="gn_payroll_structure_type_intern" model="hr.payroll.structure.type">
|
||||
<field name="name">Stagiaire</field>
|
||||
<field name="country_id" eval="False"/>
|
||||
</record>
|
||||
<record id="payroll_structure_type_benevolent" model="hr.payroll.structure.type">
|
||||
<field name="name">Administrateur bénévole</field>
|
||||
<field name="country_id" eval="False"/>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_cae" model="hr.contract.type">
|
||||
<field name="name">PEC - CAE</field>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_apprentissage" model="hr.contract.type">
|
||||
<field name="name">Contrat d'apprentissage</field>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_cdd" model="hr.contract.type">
|
||||
<field name="name">CDD</field>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_cdi" model="hr.contract.type">
|
||||
<field name="name">CDI</field>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_service_civique" model="hr.contract.type">
|
||||
<field name="name">Service civique</field>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_admin" model="hr.contract.type">
|
||||
<field name="name">Administrateur bénévole</field>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_internship" model="hr.contract.type">
|
||||
<field name="name">Stage</field>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_entretien_type_annuel" model="gn_payroll.hr.contract.entretien.type">
|
||||
<field name="name">Entretien annuel d'évaluation</field>
|
||||
<field name="month_delay">12</field>
|
||||
<field name="start_point">endofyear</field>
|
||||
<field name="auto">True</field>
|
||||
<field name="cc_ids" eval="[(6, 0, [ref('gn_payroll.cc_3442')])]"></field>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_entretien_type_periodique" model="gn_payroll.hr.contract.entretien.type">
|
||||
<field name="name">Entretien professionnel périodique</field>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_entretien_type_periodique_retour" model="gn_payroll.hr.contract.entretien.type">
|
||||
<field name="name">Entretien professionnel périodique de retour de congé long</field>
|
||||
<field name="parent_id" ref="gn_payroll_contract_entretien_type_periodique"/>
|
||||
<field name="month_delay">0</field>
|
||||
<field name="cc_ids" eval="[(6, 0, [ref('gn_payroll.cc_3442')])]"/>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_entretien_type_periodique_initial" model="gn_payroll.hr.contract.entretien.type">
|
||||
<field name="name">Entretien professionnel périodique des 2 ans</field>
|
||||
<field name="parent_id" ref="gn_payroll_contract_entretien_type_periodique"/>
|
||||
<field name="start_point">contract</field>
|
||||
<field name="month_delay">24</field>
|
||||
<field name="auto">True</field>
|
||||
<field name="cc_ids" eval="[(6, 0, [ref('gn_payroll.cc_3442')])]"/>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_entretien_type_periodique_bilan" model="gn_payroll.hr.contract.entretien.type">
|
||||
<field name="name">Entretien professionnel bilan des 6 ans</field>
|
||||
<field name="parent_id" ref="gn_payroll_contract_entretien_type_periodique"/>
|
||||
<field name="start_point">contract</field>
|
||||
<field name="month_delay">72</field>
|
||||
<field name="auto">True</field>
|
||||
<field name="cc_ids" eval="[(6, 0, [ref('gn_payroll.cc_3442')])]"/>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_entretien_type_valorisation" model="gn_payroll.hr.contract.entretien.type">
|
||||
<field name="name">Entretien de valorisation des acquis professionnels</field>
|
||||
<field name="start_point">contract</field>
|
||||
<field name="month_delay">48</field>
|
||||
<field name="generate_subcontract" eval="True"/>
|
||||
<field name="auto">True</field>
|
||||
<field name="cc_ids" eval="[(6, 0, [ref('gn_payroll.cc_3442')])]"/>
|
||||
</record>
|
||||
<record id="gn_payroll_contract_entretien_type_modif_cc" model="gn_payroll.hr.contract.entretien.type">
|
||||
<field name="name">Modification conventionnelle</field>
|
||||
</record>
|
||||
<data noupdate="1">
|
||||
<!-- Define the sequence for the Entretien model -->
|
||||
<record id="seq_entretien" model="ir.sequence">
|
||||
<field name="name">Entretien Sequence</field>
|
||||
<field name="code">gn_payroll.hr.contract.entretien</field>
|
||||
<field name="prefix">ENT</field>
|
||||
<field name="padding">5</field>
|
||||
<field name="number_increment">1</field>
|
||||
</record>
|
||||
</data>
|
||||
<menuitem
|
||||
id="menu_human_resources_configuration_contract_entretien"
|
||||
name="Entretiens"
|
||||
parent="hr.menu_hr_employee_payroll"
|
||||
sequence="35"
|
||||
action="gn_payroll.hr_contract_entretien"
|
||||
groups="hr.group_hr_manager"/>
|
||||
</odoo>
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import gn_payroll_cc
|
||||
from . import gn_payroll_company
|
||||
from . import gn_payroll_contract
|
||||
from . import gn_payroll_employee
|
||||
#from . import gn_payroll_payslip
|
@ -1,17 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ConventionCollective(models.Model):
|
||||
_name = "gn_payroll.cc"
|
||||
_description = "Convention Collective"
|
||||
_order = 'idcc, name'
|
||||
|
||||
def copy(self, default=None):
|
||||
raise UserError(_('Duplicating a company is not allowed. Please create a new company instead.'))
|
||||
|
||||
name = fields.Char(string='Nom complet de la Convention Collective', required=True, store=True, readonly=False)
|
||||
active = fields.Boolean(default=True)
|
||||
idcc = fields.Integer(string="IDCC", help='Used to order Conventions Collectives in the switcher', default=10)
|
@ -0,0 +1,226 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, api
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from datetime import date, datetime
|
||||
from odoo.exceptions import ValidationError
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class GnCalendarEvent(models.Model):
|
||||
_inherit = "calendar.event"
|
||||
is_entretien = fields.Boolean(string="Concerne un entretien professionnel", default=False)
|
||||
entretien_id = fields.Many2one("gn_payroll.hr.contract.entretien", string="Entretien professionnel associé")
|
||||
entretien_ids = fields.Many2one("gn_payroll.hr.contract.entretien", string="Entretiens professionnels associés")
|
||||
|
||||
class GnHrContract(models.Model):
|
||||
_inherit = "hr.contract"
|
||||
|
||||
#previous_contract_id = fields.Many2one("hr.contract", string="Contrat précédent (en cas d'avenant, de renouvellement ou de changement de poste)", default=False)
|
||||
#contract_lineage_sequence_number = fields.Integer(string="Numéro de séquence dans l'historique du salarié", default=0)
|
||||
computed_wage = fields.Integer(string="Salaire négocié (en fonction des éléments de la fiche de poste)", compute='_compute_wage', readonly=True, store=True)
|
||||
|
||||
#entretien_type_ids = fields.Many2many('gn_payroll.hr.contract.entretien.type', string="Catégories d'entretiens prévues au contrat")
|
||||
#entretien_sequence_number = fields.Integer(string="Entretien Sequence Number", default=1)
|
||||
|
||||
entretien_ids = fields.One2many('gn_payroll.hr.contract.entretien', inverse_name='main_contract_id', string="Entretiens liés au contrat")
|
||||
|
||||
career_ids = fields.One2many('gn_payroll.hr.contract.career', readonly=True, inverse_name='contract_id', string="Fiche de poste associée")
|
||||
|
||||
# def update_entretiens_names(self):
|
||||
# for contract in self:
|
||||
# sequence_number = 1
|
||||
# for entretien in contract.entretien_ids.sorted('start'):
|
||||
# entretien.name = 'Entretien n°{}: {}'.format(sequence_number, entretien.type_id.name )
|
||||
# sequence_number += 1
|
||||
|
||||
# @api.model
|
||||
# def _get_groups_selection(self):
|
||||
# if self.env.user.company_id.cc and self.env.user.company_id.cc.groups:
|
||||
# groups_list = [(group.strip(), group.strip()) for group in self.env.user.company_id.cc.groups.split(';')]
|
||||
# return groups_list
|
||||
# else:
|
||||
# return []
|
||||
|
||||
|
||||
# Pas à supprimer complètement, mais à reprendre
|
||||
@api.depends('indice', 'classification_group')
|
||||
def _compute_wage(self):
|
||||
for contract in self:
|
||||
contract.computed_wage = contract.wage
|
||||
return True
|
||||
if contract.company_id.cc and contract.company_id.cc.idcc =="3442":
|
||||
group_coeff = cc.actual_coeffs.filtered(lambda r: r.group == contract.classification_group).ensure_one()['coeff_min']
|
||||
if group_coeff > employee.contract_id.indice:
|
||||
raise UserError("L'indice minimum conventionnel pour le poste est %s, veuillez ajuster l'indice du contract en conséquence.", group_coeff)
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
contract = super(GnHrContract, self).create(vals)
|
||||
if not contract.parent_contract_id:
|
||||
cc_id = contract.company_id.cc.id
|
||||
entretien_types = self.env['gn_payroll.hr.contract.entretien.type'].search([
|
||||
('cc_ids', 'in', [cc_id]),
|
||||
('auto', '=', True)
|
||||
])
|
||||
for entretien_type in entretien_types:
|
||||
self.env['gn_payroll.hr.contract.entretien'].create({
|
||||
'employee_id': contract.employee_id.user_partner_id.id,
|
||||
'main_contract_id': contract.id,
|
||||
'type_id': entretien_type.id,
|
||||
})
|
||||
#contract.entretien_sequence_number += 1
|
||||
contract_lineage_sequence_number += 1
|
||||
return contract
|
||||
|
||||
|
||||
class EntretienType(models.Model):
|
||||
_name='gn_payroll.hr.contract.entretien.type'
|
||||
_description = "Type d'entretien d'évaluation ou de modification du contrat"
|
||||
|
||||
cc_ids = fields.Many2many('gn_payroll.cc', string="Conventions collectives rattachées")
|
||||
name = fields.Char(string="Type de l'entretien", required = True)
|
||||
auto = fields.Boolean("Est généré automatiquement à la création du contrat")
|
||||
start_point = fields.Selection(string="Point de départ des entretiens", selection=[('endofyear', "En fin d'année"), ('contract', "À la date anniversaire du contrat"), ('absence', "Au retour d'un congé long")])
|
||||
month_delay = fields.Integer(string="Nombre de mois entre chaque entretien")
|
||||
generate_subcontract = fields.Boolean("Génère la création d'un avenant au contrat")
|
||||
parent_id = fields.Many2one('gn_payroll.hr.contract.entretien.type', string="Type d'entretien parent", ondelete="cascade")
|
||||
child_ids = fields.One2many('gn_payroll.hr.contract.entretien.type', 'parent_id', string="Types d'entretien enfants")
|
||||
|
||||
|
||||
@api.constrains('parent_id')
|
||||
def _check_parent_id(self):
|
||||
for record in self:
|
||||
if record.parent_id == record:
|
||||
raise ValidationError("A record cannot select itself as a parent.")
|
||||
|
||||
if record.parent_id and record.parent_id.parent_id:
|
||||
raise ValidationError("You cannot select a parent type which already has a parent.")
|
||||
|
||||
@api.onchange('parent_id')
|
||||
def _onchange_parent_id(self):
|
||||
if self.parent_id:
|
||||
if self.parent_id == self:
|
||||
self.parent_id = False
|
||||
return {
|
||||
'warning': {
|
||||
'title': "Invalid Parent Selection",
|
||||
'message': "A record cannot select itself as a parent.",
|
||||
},
|
||||
'domain': {'parent_id': domain}
|
||||
}
|
||||
if self.parent_id.parent_id:
|
||||
self.parent_id = False
|
||||
return {
|
||||
'warning': {
|
||||
'title': "Invalid Parent Selection",
|
||||
'message': "You cannot select a parent type which already has a parent.",
|
||||
}
|
||||
}
|
||||
|
||||
class Entretien(models.Model):
|
||||
_name = 'gn_payroll.hr.contract.entretien'
|
||||
_description = "Entretien légal d'évaluation ou de modification du contrat"
|
||||
|
||||
ref = fields.Char(string="Référence", readonly=True)
|
||||
name = fields.Char(string="Titre", readonly=True)
|
||||
employee_id = fields.Many2one('res.partner', string="Employé", required=True, domain=[("employee_ids", "!=", False)], relation="contract_entretien_employee_rel")
|
||||
other_participant_ids = fields.Many2many('res.partner', string="Autres participants à l'entretien (délégué salariale, etc.)", relation="contract_entretien_other_participant_rel")
|
||||
interviewer_id = fields.Many2one('res.partner', domain=[("employee_ids", "!=", False)], string="Interviewer", relation="contract_entretien_interviewer_rel")
|
||||
other_interviewer_ids = fields.Many2many('res.partner', domain=[("employee_ids", "!=", False)], string="Participants à l'entretien", relation="contract_entretien_other_interviewer_rel")
|
||||
main_contract_id = fields.Many2one('hr.contract', string = "Contrat concerné", required=True)
|
||||
running_contract_id = fields.Many2one('hr.contract', string="Avenant concerné")
|
||||
|
||||
type_id = fields.Many2one('gn_payroll.hr.contract.entretien.type', string="Type d'entretien", required=True)
|
||||
first_date = fields.Date(string="Premier entretien", compute='_compute_first_date', store=True)
|
||||
next_date = fields.Date(string="Date du prochain entretien", compute='_compute_next_entretien_date', store=True)
|
||||
|
||||
report = fields.Html(string="Compte-rendu")
|
||||
|
||||
event_ids = fields.One2many('calendar.event', 'entretien_ids', string="Rendez-vous programmés")
|
||||
event_id = fields.One2many('calendar.event', 'entretien_id', string="Rendez-vous programmé", compute='_get_date_from_event', store=True)
|
||||
start = fields.Datetime("Date et heure du rendez-vous", compute='_get_date_from_event', store=True)
|
||||
|
||||
generate_subcontract = fields.Boolean(
|
||||
string="Génére la création d'un avenant",
|
||||
default=lambda self: self._get_default_generate_subcontract()
|
||||
)
|
||||
generation_start = fields.Datetime(string="Date de prise en compte des aménagements")
|
||||
generated_contract_id = fields.Many2one('hr.contract', string="Avenant généré")
|
||||
generated_career_id = fields.Many2one('gn_payroll.hr.contract.career', string="Fiche de poste")
|
||||
|
||||
def _get_default_generate_subcontract(self):
|
||||
return self.type_id.generate_subcontract if self.type_id else False
|
||||
|
||||
def update_main_contract(self):
|
||||
# Updates entretien's num by date for employee,
|
||||
# Called in _get_date_from_event on event_ids access
|
||||
for entretien in self:
|
||||
if entretien['main_contract_id']:
|
||||
contract = self.env['hr.contract'].browse(entretien['main_contract_id'].id)
|
||||
contract.update_entretiens_names()
|
||||
|
||||
@api.depends('type_id.start_point', 'type_id.month_delay')
|
||||
def _compute_first_date(self):
|
||||
for entretien in self:
|
||||
if entretien.type_id and entretien.type_id.start_point:
|
||||
start_point = entretien.type_id.start_point
|
||||
if start_point == "contract" and entretien.main_contract_id.date_start and entretien.type_id.month_delay:
|
||||
contract_start_date = fields.Date.from_string(entretien.main_contract_id.date_start)
|
||||
entretien.first_date = contract_start_date + relativedelta(months=+entretien.type_id.month_delay)
|
||||
elif start_point == "endofyear":
|
||||
today = fields.Date.today()
|
||||
entretien.first_date = date(today.year, 12, today.day)
|
||||
elif start_point == "absence":
|
||||
entretien.first_date = fields.Date.today()
|
||||
|
||||
@api.depends('event_ids')
|
||||
def _get_date_from_event(self):
|
||||
for entretien in self:
|
||||
try:
|
||||
if len(entretien.event_ids) > 0 and entretien.event_ids[0].start:
|
||||
#entretien.event_id = self.env['calendar.event'].browse(entretien.event_ids[0].id)
|
||||
entretien.event_id = entretien.event_ids[0]
|
||||
_logger.info("Entretien has event_id: %s", entretien.event_id)
|
||||
entretien.start = datetime.combine(entretien.event_ids[0].start, datetime.min.time())
|
||||
else:
|
||||
entretien.start = datetime.combine(entretien.first_date, datetime.min.time()) if entretien.first_date else datetime.now()
|
||||
except Exception as e:
|
||||
_logger.error("Error occurred while setting start date: %s", e)
|
||||
entretien.start = datetime.combine(fields.Date.today(), datetime.min.time())
|
||||
|
||||
_logger.info("Date defined as start in entretien: %s", entretien.start)
|
||||
self.update_main_contract()
|
||||
|
||||
@api.depends('first_date', 'type_id.month_delay')
|
||||
def _compute_next_entretien_date(self):
|
||||
for entretien in self:
|
||||
if entretien.first_date and entretien.type_id.month_delay:
|
||||
next_entretien_date = fields.Date.from_string(entretien.first_date) + relativedelta(months=entretien.type_id.month_delay)
|
||||
entretien.next_date = fields.Date.to_string(next_entretien_date)
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
|
||||
result = super(Entretien, self).create(vals)
|
||||
employee_type = self.env['res.partner'].browse(vals['employee_id'])
|
||||
_logger.info("Employee id is : %s", employee_type.company_type)
|
||||
# Generate a sequence number for the name field
|
||||
if not vals.get('ref'):
|
||||
vals['ref'] = self.env['ir.sequence'].next_by_code('gn_payroll.hr.contract.entretien') or "New"
|
||||
_logger.info("Ref for entretien : %s", vals['ref'])
|
||||
|
||||
# Fetch contract to regenerated associated entretiens names
|
||||
if vals.get('main_contract_id'):
|
||||
contract = self.env['hr.contract'].browse(vals['main_contract_id'])
|
||||
contract.update_entretiens_names()
|
||||
|
||||
if vals.get('event_ids'):
|
||||
existing_entretien = self.search([('event_ids', 'in', vals['event_id'])], limit=1)
|
||||
if existing_entretien:
|
||||
raise ValidationError("An entretien is already associated with this event.")
|
||||
|
||||
return result
|
||||
|
@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, api
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class ContractCareer(models.Model):
|
||||
_name = "gn_payroll.career"
|
||||
_description = "Analyse du poste"
|
||||
_order = 'start_date, employee_id'
|
||||
|
||||
start_date = fields.Date('From', required=True, default=lambda self: fields.Date.today())
|
||||
contract_id = fields.Many2one('gn_payroll.hr.contract', 'career_id', required=True, string="Contrat ou Avenant Associé")
|
||||
main_contract_id = fields.Many2one('gn_payroll.hr.contract', compute='_compute_main_contract', string="Contract principal associé")
|
||||
|
||||
entretien_id = fields.Many2one('gn_payroll.hr.contract.entretien', string="Entretien professionnel associé")
|
||||
|
||||
classification_group = fields.Selection(selection='_get_groups_selection', string="Groupe associé au coefficient")
|
||||
indice = fields.Integer(string="indice du salaire négocié (nombre de points)")
|
||||
|
||||
|
||||
@api.constrains('entretien_id')
|
||||
def _check_entretien_uniqueness(self):
|
||||
for record in self:
|
||||
if record.entretien_id:
|
||||
# Check if any other career records have the same entretien_id
|
||||
existing = self.search([
|
||||
('id', '!=', record.id),
|
||||
('entretien_id', '=', record.entretien_id.id),
|
||||
])
|
||||
if existing:
|
||||
raise ValidationError("An entretien can only be associated with one career.")
|
||||
|
||||
@api.constrains('entretien_id')
|
||||
def _check_contract_uniqueness(self):
|
||||
for record in self:
|
||||
if record.contract_id:
|
||||
# Check if any other career records have the same entretien_id
|
||||
existing = self.search([
|
||||
('id', '!=', record.id),
|
||||
('contract_id', '=', record.contract_id.id),
|
||||
])
|
||||
if existing:
|
||||
raise ValidationError("An contract can only be associated with one career.")
|
||||
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
career = super(ContractCareer, self).create(vals)
|
||||
contract_id = vals.get('contract_id')
|
||||
if contract_id:
|
||||
contract = self.env['gn_payroll.hr.contract'].browse(contract_id)
|
||||
if contract.parent_contract_id:
|
||||
career.write({'main_contract_id': contract.parent_contract_id.id})
|
||||
return career
|
@ -1,12 +1,17 @@
|
||||
from odoo import models, fields
|
||||
from odoo import models, fields, api
|
||||
|
||||
class GnPayrollHrEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
employee_type = fields.Selection([
|
||||
('employee', 'Employé'),
|
||||
('student', 'Étudiant'),
|
||||
('trainee', 'Stagiaire'),
|
||||
('volunteer', 'Volontaire'),
|
||||
('benevolent', 'Bénévole'),
|
||||
], string='Employee Type', default='employee', required=True,
|
||||
help="The employee type. Although the primary purpose may seem to categorize employees, this field has also an impact in the Contract History. Only Employee type is supposed to be under contract and will have a Contract History.")
|
||||
|
||||
anciennete_start_date = fields.Date(compute='_compute_anciennete_start_date', groups='hr_group.hr_user', store=True)
|
||||
|
||||
@api.depends('contract_ids.state', 'contract_ids.date_start')
|
||||
def _compute_anciennete_start_date(self):
|
||||
for employee in self:
|
||||
if employee.contract_id:
|
||||
if employee.contract_id.parent_contract_id:
|
||||
employee.anciennete_start_date = employee.contract_id.parent_contract_id.date_start
|
||||
else:
|
||||
employee.anciennete_start_date = employee.contract_id.date_start
|
||||
else:
|
||||
employee.anciennete_start_date = False
|
@ -0,0 +1,59 @@
|
||||
from odoo import models, api
|
||||
from datetime import datetime, time
|
||||
|
||||
class GnPayrollHrPayslip(models.Model):
|
||||
_inherit = 'hr.payslip'
|
||||
|
||||
def _compute_sickness_days(self, contract, day_from, day_to):
|
||||
"""
|
||||
Worked days computation
|
||||
@return: returns a list containing the total worked_days for the period
|
||||
of the payslip. This returns the FULL work days expected for the resource
|
||||
calendar selected for the employee (it don't substract leaves by default).
|
||||
"""
|
||||
work_data = contract.employee_id._get_work_days_data_batch(
|
||||
day_from,
|
||||
day_to,
|
||||
calendar=contract.resource_calendar_id,
|
||||
compute_leaves=False,
|
||||
)
|
||||
return {
|
||||
"name": "Sickness days",
|
||||
"sequence": 1,
|
||||
"code": "SICKNESS",
|
||||
"number_of_days": work_data[contract.employee_id.id]["days"],
|
||||
"number_of_hours": work_data[contract.employee_id.id]["hours"],
|
||||
"contract_id": contract.id,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def get_worked_day_lines(self, contracts, date_from, date_to):
|
||||
"""
|
||||
@param contracts: Browse record of contracts
|
||||
@return: returns a list of dict containing the input that should be
|
||||
applied for the given contract between date_from and date_to
|
||||
"""
|
||||
res = []
|
||||
for contract in contracts.filtered(
|
||||
lambda contract: contract.resource_calendar_id
|
||||
):
|
||||
day_from = datetime.combine(date_from, time.min)
|
||||
day_to = datetime.combine(date_to, time.max)
|
||||
day_contract_start = datetime.combine(contract.date_start, time.min)
|
||||
# Support for the hr_public_holidays module.
|
||||
contract = contract.with_context(
|
||||
employee_id=self.employee_id.id, exclude_public_holidays=True
|
||||
)
|
||||
# only use payslip day_from if it's greather than contract start date
|
||||
if day_from < day_contract_start:
|
||||
day_from = day_contract_start
|
||||
# == compute leave days == #
|
||||
leaves = self._compute_leave_days(contract, day_from, day_to)
|
||||
res.extend(leaves)
|
||||
# == compute worked days == #
|
||||
attendances = self._compute_worked_days(contract, day_from, day_to)
|
||||
res.append(attendances)
|
||||
# == compute sicknss days == #
|
||||
sickness = self._compute_sickness_days(contract, day_from, day_to)
|
||||
res.append(sickness)
|
||||
return res
|
@ -1,3 +1,5 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_gn_payroll_cc_user,gn_payroll_cc_user,model_gn_payroll_cc,base.group_user,1,0,0,0
|
||||
acces_gn_payroll_cc_admin,gn_payroll_cc_admin,model_gn_payroll_cc,hr_contract.group_hr_contract_manager,1,1,0,0
|
||||
access_gn_payroll_hr_contract_entretien_user,gn_payroll_hr_contract_entretien_user,model_gn_payroll_hr_contract_entretien,base.group_user,1,0,0,0
|
||||
access_gn_payroll_hr_contract_entretien_admin,gn_payroll_hr_contract_entretien_admin,model_gn_payroll_hr_contract_entretien,hr_contract.group_hr_contract_manager,1,1,1,1
|
||||
access_gn_payroll_hr_contract_entretien_type_user,gn_payroll_hr_contract_entretien_type_user,model_gn_payroll_hr_contract_entretien_type,base.group_user,1,0,0,0
|
||||
access_gn_payroll_hr_contract_entretien_type_admin,gn_payroll_hr_contract_entretien_type_admin,model_gn_payroll_hr_contract_entretien_type,hr_contract.group_hr_contract_manager,1,1,1,1
|
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="gn_view_calendar_event_form" model="ir.ui.view">
|
||||
<field name="name">gncalendar.event.form</field>
|
||||
<field name="model">calendar.event</field>
|
||||
<field name="inherit_id" ref="calendar.view_calendar_event_form"/>
|
||||
<field name="priority" eval="1"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='categ_ids']" position="after">
|
||||
<field name="is_entretien"/>
|
||||
<field name="entretien_ids" domain="[('event_id', '=', False)]" attrs="{'invisible': [('is_entretien', '=', False)]}"/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="gn_payroll_entretien_form_view" model="ir.ui.view">
|
||||
<field name="name">gn_payroll.hr.contract.entretien.form</field>
|
||||
<field name="model">gn_payroll.hr.contract.entretien</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Entretiens">
|
||||
<sheet string="Entretien">
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="Entretien" />
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<field name="ref"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="interviewer_id"/>
|
||||
<field name="other_interviewer_ids"/>
|
||||
<field name="other_participant_ids"/>
|
||||
<field name="event_ids"/>
|
||||
<field name="event_id"/>
|
||||
<field name="start"/>
|
||||
<field name="first_date"/>
|
||||
<field name="next_date"/>
|
||||
<field name="main_contract_id"/>
|
||||
<field name="running_contract_id"/>
|
||||
<field name="generated_contract_id"/>
|
||||
<field name="type_id"/>
|
||||
<field name="report"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_payroll.hr_contract_entretien" model="ir.actions.act_window">
|
||||
<field name="name">Entretiens</field>
|
||||
<field name="res_model">gn_payroll.hr.contract.entretien</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
<record id="gn_payroll.hr_contract_entretien_type" model="ir.actions.act_window">
|
||||
<field name="name">Types d'Entretiens</field>
|
||||
<field name="res_model">gn_payroll.hr.contract.entretien.type</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
</odoo>
|
Loading…
Reference in New Issue