mirror of
https://github.com/bringout/oca-ocb-hr.git
synced 2026-04-27 04:32:03 +02:00
537 lines
26 KiB
Python
537 lines
26 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
from collections import defaultdict
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
from odoo import api, fields, models
|
|
|
|
from odoo.exceptions import ValidationError
|
|
from odoo.fields import Domain
|
|
|
|
|
|
class HrIndividualSkillMixin(models.AbstractModel):
|
|
_name = 'hr.individual.skill.mixin'
|
|
_description = "Skill level"
|
|
_order = "skill_type_id, skill_level_id"
|
|
_rec_name = "skill_id"
|
|
|
|
def _linked_field_name(self):
|
|
raise NotImplementedError()
|
|
|
|
def _get_passive_fields(self):
|
|
"""
|
|
Return additional passive fields to be included during (versioned)skill creation.
|
|
|
|
Passive fields are preserved/included when new skill versions are created due to changes in any of the
|
|
core/active fields (linked_field, skill_id, skill_level_id, skill_type_id),but modifying them DOES NOT
|
|
trigger new (versioned)skill creation.
|
|
|
|
Core/Active fields (linked_field, skill_id, skill_level_id, skill_type_id) are automatically preserved
|
|
and should NOT be included here.
|
|
|
|
:return: List of field names to copy to new skills
|
|
:rtype: list[str]
|
|
"""
|
|
return []
|
|
|
|
def _can_edit_certification_validity_period(self):
|
|
# If True, the overlapping constraint on a certification is released:
|
|
# two certifications are overlapping each other if they have the same skill_level_id, valid_from and valid_to.
|
|
# This behavior is wanted when the user can change the validity.
|
|
return True
|
|
|
|
def _default_skill_type_id(self):
|
|
if self.env.context.get('certificate_skill', False):
|
|
return self.env['hr.skill.type'].search([('is_certification', '=', True)], limit=1)
|
|
return self.env['hr.skill.type'].search([], limit=1)
|
|
|
|
skill_id = fields.Many2one('hr.skill', compute='_compute_skill_id', store=True,
|
|
domain="[('skill_type_id', '=', skill_type_id)]", readonly=False, required=True, ondelete='cascade')
|
|
skill_level_id = fields.Many2one('hr.skill.level', compute='_compute_skill_level_id',
|
|
domain="[('skill_type_id', '=', skill_type_id)]", store=True, readonly=False, required=True, ondelete='cascade')
|
|
skill_type_id = fields.Many2one('hr.skill.type',
|
|
default=_default_skill_type_id,
|
|
required=True, ondelete='cascade')
|
|
level_progress = fields.Integer(related='skill_level_id.level_progress')
|
|
color = fields.Integer(related="skill_type_id.color")
|
|
valid_from = fields.Date(string="Validity Start", default=fields.Date.today())
|
|
valid_to = fields.Date(string="Validity Stop")
|
|
levels_count = fields.Integer(related="skill_type_id.levels_count")
|
|
certification_skill_type_count = fields.Integer(compute="_compute_certification_skill_type_count",
|
|
export_string_translation=False)
|
|
is_certification = fields.Boolean(related="skill_type_id.is_certification",
|
|
export_string_translation=False) # if is_certification change the model will not trigger the constrains
|
|
display_warning_message = fields.Boolean()
|
|
|
|
@api.constrains(lambda self: [
|
|
'valid_from', 'valid_to', 'skill_id', 'skill_type_id', 'skill_level_id', self._linked_field_name()
|
|
])
|
|
def _check_not_overlapping_regular_skill(self):
|
|
"""
|
|
The following is the core functionality and difference for the two models
|
|
Skills:
|
|
1. There can only be one active skill for each skill_id, f.ex only one level of English allowed.
|
|
2. Skills should not be deleted, unless they were created within the last 24 hours. Skills should instead
|
|
be archived to preserve the history of the skills linked to that particular record.
|
|
3. Skills should not be written to, instead the previous skill should be archived and a new skill with
|
|
the new values should be created. This is again to preserve the history of skills on the record.
|
|
Certifications:
|
|
1. There can be many certifications with the same skill_id and skill_level as long as the valid_from and
|
|
valid_to fields are different, e.g. "Odoo:Certified 2025-1-1 to 2025-12-31" can exist alongside
|
|
"Odoo:Certified 2024-6-1 to 2025-5-31".
|
|
2. Certifications can be deleted at any point.
|
|
3. Certifications should not be written to, instead the previous certification should be archived and a new
|
|
certification with the new values should be created.
|
|
For both models:
|
|
1. There should not be multiple records with exactly the same values.
|
|
2. An Active skill/certification is one with the valid_to field either unset or set to a date in the future
|
|
"""
|
|
overlapping_dict = self._get_overlapping_individual_skill([{
|
|
f"{self._linked_field_name()}": skill_ind[self._linked_field_name()].id,
|
|
"skill_id": skill_ind.skill_id.id,
|
|
"id": skill_ind.id,
|
|
"valid_from": skill_ind.valid_from,
|
|
"valid_to": skill_ind.valid_to,
|
|
"skill_level_id": skill_ind.skill_level_id.id,
|
|
"is_certification": skill_ind.is_certification
|
|
}
|
|
for skill_ind in self])
|
|
if overlapping_dict:
|
|
errors = []
|
|
for existing_ind_skill, new_ind_skills in overlapping_dict.items():
|
|
errors.append(
|
|
f"• {', '.join([str(ind_skill) for ind_skill in new_ind_skills])} conflicts with the existing skill/certification {existing_ind_skill.display_name} from {existing_ind_skill.valid_from} to {existing_ind_skill.valid_to}",
|
|
)
|
|
|
|
error_msg = self.env._(
|
|
"The following skills can't be created as they overlap or exactly match existing skills:\n%(collisions)s",
|
|
collisions="\n".join(errors),
|
|
)
|
|
raise ValidationError(error_msg)
|
|
|
|
def _get_overlapping_individual_skill(self, vals_list):
|
|
can_edit_certification_validity_period = self._can_edit_certification_validity_period()
|
|
matching_skill_domain = Domain.FALSE
|
|
overlapping_dict = defaultdict(list)
|
|
certification_dict = defaultdict(list)
|
|
regular_dict = defaultdict(list)
|
|
for individual_skill_vals in vals_list:
|
|
ind_domain = Domain.AND([
|
|
Domain(f"{self._linked_field_name()}.id", "=", individual_skill_vals[self._linked_field_name()]),
|
|
Domain("skill_id.id", "=", individual_skill_vals['skill_id']),
|
|
Domain("id", "!=", individual_skill_vals['id']),
|
|
])
|
|
|
|
if can_edit_certification_validity_period and individual_skill_vals['is_certification']:
|
|
ind_domain = Domain.AND([
|
|
ind_domain,
|
|
Domain("skill_level_id.id", "=", individual_skill_vals['skill_level_id']),
|
|
Domain('valid_from', '=', individual_skill_vals['valid_from']),
|
|
Domain('valid_to', '=', individual_skill_vals['valid_to']),
|
|
])
|
|
key = (
|
|
individual_skill_vals[self._linked_field_name()],
|
|
individual_skill_vals['skill_id'],
|
|
individual_skill_vals['skill_level_id'],
|
|
fields.Date.from_string(individual_skill_vals['valid_from']),
|
|
fields.Date.from_string(individual_skill_vals['valid_to']),
|
|
)
|
|
certification_dict[key].append(individual_skill_vals)
|
|
else:
|
|
ind_domain = Domain.AND([
|
|
ind_domain,
|
|
Domain.OR([
|
|
Domain.AND([
|
|
Domain('valid_from', '<=', individual_skill_vals['valid_from']),
|
|
Domain.OR([
|
|
Domain('valid_to', '=', False),
|
|
Domain('valid_to', '>=', individual_skill_vals['valid_from']),
|
|
]),
|
|
]),
|
|
Domain.AND([
|
|
Domain('valid_from', '<=', individual_skill_vals['valid_to']),
|
|
Domain.OR([
|
|
Domain('valid_to', '=', False),
|
|
Domain('valid_to', '>=', individual_skill_vals['valid_to']),
|
|
]),
|
|
]),
|
|
])
|
|
])
|
|
|
|
key = (
|
|
individual_skill_vals[self._linked_field_name()],
|
|
individual_skill_vals['skill_id'],
|
|
)
|
|
regular_dict[key].append(individual_skill_vals)
|
|
|
|
matching_skill_domain = Domain.OR([matching_skill_domain, ind_domain])
|
|
matching_individual_skills = self.env[self._name].search(matching_skill_domain)
|
|
for matching_ind_skill in matching_individual_skills:
|
|
if can_edit_certification_validity_period and matching_ind_skill.is_certification:
|
|
similar_certifications = certification_dict.get((
|
|
matching_ind_skill[self._linked_field_name()].id,
|
|
matching_ind_skill.skill_id.id,
|
|
matching_ind_skill.skill_level_id.id,
|
|
fields.Date.from_string(matching_ind_skill.valid_from),
|
|
fields.Date.from_string(matching_ind_skill.valid_to),
|
|
))
|
|
if similar_certifications:
|
|
overlapping_dict[matching_ind_skill].extend(similar_certifications)
|
|
else:
|
|
similar_regular_skills = regular_dict.get((
|
|
matching_ind_skill[self._linked_field_name()].id,
|
|
matching_ind_skill.skill_id.id,
|
|
), [])
|
|
for similar_regular_skill in similar_regular_skills:
|
|
if (matching_ind_skill.valid_from <= similar_regular_skill['valid_from'] and
|
|
(not matching_ind_skill.valid_to or
|
|
matching_ind_skill.valid_to >= similar_regular_skill['valid_from']
|
|
)) or (matching_ind_skill.valid_from <= similar_regular_skill['valid_to'] and
|
|
(not matching_ind_skill.valid_to or
|
|
matching_ind_skill.valid_to >= similar_regular_skill['valid_to']
|
|
)):
|
|
overlapping_dict[matching_ind_skill].append(similar_regular_skill)
|
|
return overlapping_dict
|
|
|
|
@api.constrains('valid_from', 'valid_to')
|
|
def _check_date(self):
|
|
error_ind_skill_msg = ""
|
|
for ind_skill in self:
|
|
if ind_skill.valid_to and ind_skill.valid_from > ind_skill.valid_to:
|
|
error_ind_skill_msg += self.env._("• %(skill_name)s from %(valid_from)s to %(valid_to)s",
|
|
skill_name=ind_skill.display_name, valid_from=ind_skill.valid_from, valid_to=ind_skill.valid_to
|
|
)
|
|
if error_ind_skill_msg:
|
|
raise ValidationError(self.env._("The following skills have their valid stop date prior to their valid start date:\n") + error_ind_skill_msg)
|
|
|
|
@api.constrains('skill_id', 'skill_type_id')
|
|
def _check_skill_type(self):
|
|
for record in self:
|
|
if record.skill_id not in record.skill_type_id.skill_ids:
|
|
raise ValidationError(self.env._("The skill %(name)s and skill type %(type)s don't match",
|
|
name=record.skill_id.name, type=record.skill_type_id.name))
|
|
|
|
@api.constrains('skill_type_id', 'skill_level_id')
|
|
def _check_skill_level(self):
|
|
for record in self:
|
|
if record.skill_level_id not in record.skill_type_id.skill_level_ids:
|
|
raise ValidationError(self.env._("The skill level %(level)s is not valid for skill type: %(type)s",
|
|
level=record.skill_level_id.name, type=record.skill_type_id.name))
|
|
|
|
def _compute_certification_skill_type_count(self):
|
|
certification_skill_type_count = self.env['hr.skill.type'].search_count(domain=[('is_certification', '=', True)])
|
|
self.certification_skill_type_count = certification_skill_type_count
|
|
|
|
# To reset the validity period if the skill become certified or uncertified
|
|
@api.onchange('is_certification')
|
|
def _onchange_is_certification(self):
|
|
self.valid_from = fields.Date.today()
|
|
if not self.is_certification:
|
|
self.valid_to = False
|
|
|
|
@api.depends('skill_type_id')
|
|
def _compute_skill_id(self):
|
|
for record in self:
|
|
if record.skill_type_id:
|
|
record.skill_id = record.skill_type_id.skill_ids[0] if record.skill_type_id.skill_ids else False
|
|
else:
|
|
record.skill_id = False
|
|
|
|
@api.depends('skill_id')
|
|
def _compute_skill_level_id(self):
|
|
for record in self:
|
|
if not record.skill_id:
|
|
record.skill_level_id = False
|
|
else:
|
|
skill_levels = record.skill_type_id.skill_level_ids
|
|
record.skill_level_id = skill_levels.filtered('default_level') or skill_levels[0] if skill_levels else False
|
|
|
|
@api.depends('skill_id', 'skill_level_id')
|
|
def _compute_display_name(self):
|
|
for individual_skill in self:
|
|
individual_skill.display_name = f"{individual_skill.skill_id.name}: {individual_skill.skill_level_id.name}"
|
|
|
|
@api.onchange('valid_to', 'valid_from')
|
|
def _onchange_valid_date(self):
|
|
self.display_warning_message = self.valid_to and self.valid_from and self.valid_to < self.valid_from
|
|
|
|
def _expire_individual_skills(self):
|
|
"""
|
|
This function archive all individual skill in self.
|
|
If the individual skill is not expired (valid_to < today) then valid_to will be set to yesterday if
|
|
it's possible (not break a constraint)
|
|
Else the individual skill is delete
|
|
|
|
Example:
|
|
An individual already have the skill English A2 (added one month ago) and we want to delete it
|
|
output: [[1, id('English A2'), {'valid_to': yesterday}]]
|
|
@return {List[COMMANDS]} List of WRITE, UNLINK commands
|
|
"""
|
|
yesterday = fields.Date.today() - relativedelta(days=1)
|
|
to_remove = self.env[self._name]
|
|
to_archive = self.env[self._name]
|
|
for individual_skill in self:
|
|
if individual_skill.valid_from >= yesterday or (individual_skill.valid_to and individual_skill.valid_to <= yesterday):
|
|
to_remove += individual_skill
|
|
else:
|
|
to_archive += individual_skill
|
|
if to_archive:
|
|
overlapping_dict = self._get_overlapping_individual_skill([{
|
|
f"{self._linked_field_name()}": skill[self._linked_field_name()].id,
|
|
"skill_id": skill.skill_id.id,
|
|
"id": skill.id,
|
|
"valid_from": skill.valid_from,
|
|
"valid_to": yesterday,
|
|
"skill_level_id": skill.skill_level_id.id,
|
|
"is_certification": skill.is_certification
|
|
} for skill in to_archive])
|
|
new_overlapped_skill_ids = []
|
|
for new_skills in overlapping_dict.values():
|
|
for new_skill in new_skills:
|
|
new_overlapped_skill_ids.append(new_skill['id'])
|
|
changed_to_remove = to_archive.filtered(lambda ind_skill: ind_skill.id in new_overlapped_skill_ids)
|
|
to_archive -= changed_to_remove
|
|
to_remove += changed_to_remove
|
|
return [[2, skill.id] for skill in to_remove] + [[1, skill.id, {'valid_to': yesterday}] for skill in to_archive]
|
|
|
|
def _create_individual_skills(self, vals_list):
|
|
"""
|
|
This function transform CREATE commands into CREATE, WRITE and UNLINK commands in order to keep the
|
|
logs and to follow the constraints
|
|
|
|
Example:
|
|
An individual already have the skill English A2 (added one month ago) and we want to add the skill English B1
|
|
This method will transform:
|
|
{linked_field: id, skill_id: id('English'), skill_level_id: id('B1') skill_type_id: id('Languages')}
|
|
into
|
|
[
|
|
[1, id('English A2'), {'valid_to': yesterday}],
|
|
[0, 0, {
|
|
linked_field: id,
|
|
skill_id: id('English'),
|
|
skill_level_id: id('B1'),
|
|
skill_type_id: id('Languages')}
|
|
]
|
|
]
|
|
@param {List[vals]} vals_list: list of right leaf of CREATE commands
|
|
@return {List[COMMANDS]} List of CREATE, WRITE, UNLINK commands
|
|
"""
|
|
can_edit_certification_validity_period = self._can_edit_certification_validity_period()
|
|
seen_skills = set()
|
|
skills_to_archive = self.env[self._name]
|
|
vals_to_return = []
|
|
|
|
validity_domain = Domain.OR(
|
|
[
|
|
Domain("valid_to", "=", False),
|
|
Domain("valid_to", ">=", fields.Date.today()),
|
|
]
|
|
)
|
|
|
|
if can_edit_certification_validity_period:
|
|
validity_domain = Domain.OR([
|
|
validity_domain,
|
|
Domain("is_certification", "=", True),
|
|
])
|
|
|
|
existing_skills_domain = Domain.AND(
|
|
[
|
|
Domain.OR(
|
|
[
|
|
Domain.AND(
|
|
[
|
|
Domain(f"{self._linked_field_name()}", "=", vals.get(self._linked_field_name(), False)),
|
|
Domain("skill_id", "=", vals.get("skill_id", False)),
|
|
]
|
|
)
|
|
for vals in vals_list
|
|
]
|
|
),
|
|
validity_domain
|
|
]
|
|
)
|
|
|
|
existing_skills = self.env[self._name].search(existing_skills_domain)
|
|
existing_skills_grouped = existing_skills.grouped(
|
|
lambda skill: (skill[self._linked_field_name()].id, skill.skill_id.id)
|
|
)
|
|
|
|
if can_edit_certification_validity_period:
|
|
existing_certifications = existing_skills.filtered(lambda s: s.is_certification)
|
|
certification_set = {}
|
|
for cert in existing_certifications:
|
|
key = (
|
|
cert[self._linked_field_name()].id,
|
|
cert.skill_id.id,
|
|
cert.skill_level_id.id,
|
|
fields.Date.from_string(cert.valid_from),
|
|
fields.Date.from_string(cert.valid_to),
|
|
)
|
|
certification_set[key] = cert
|
|
|
|
certification_types = set(
|
|
self.env["hr.skill.type"]
|
|
.browse([vals["skill_type_id"] for vals in vals_list])
|
|
.filtered("is_certification")
|
|
.ids
|
|
)
|
|
for vals in vals_list:
|
|
individual_skill_id = vals.get(self._linked_field_name(), False)
|
|
skill_id = vals["skill_id"]
|
|
skill_type_id = vals["skill_type_id"]
|
|
skill_level_id = vals["skill_level_id"]
|
|
valid_from = fields.Date.from_string(vals.get("valid_from"))
|
|
valid_to = fields.Date.from_string(vals.get("valid_to"))
|
|
|
|
if can_edit_certification_validity_period:
|
|
is_certificate = skill_type_id in certification_types
|
|
else:
|
|
is_certificate = False
|
|
|
|
skill_key = (individual_skill_id, skill_id, valid_from, valid_to)
|
|
|
|
# Remove duplicate skills
|
|
if skill_key in seen_skills:
|
|
continue
|
|
seen_skills.add(skill_key)
|
|
|
|
if is_certificate:
|
|
key = (
|
|
individual_skill_id,
|
|
skill_id,
|
|
skill_level_id,
|
|
valid_from,
|
|
valid_to,
|
|
)
|
|
# Remove duplicate certification
|
|
if certification_set.get(key):
|
|
continue
|
|
else:
|
|
# Archive existing regular skill if the person already have one with the same skill
|
|
if existing_skill := existing_skills_grouped.get((individual_skill_id, skill_id)):
|
|
skills_to_archive += existing_skill
|
|
|
|
vals_to_return.append(vals)
|
|
|
|
return skills_to_archive._expire_individual_skills() + [[0, 0, new_create_val] for new_create_val in vals_to_return]
|
|
|
|
def _write_individual_skills(self, commands):
|
|
"""
|
|
Transform a list of write commands into a list of create, write and unlink commands according to the logic of
|
|
how skills should behave. The relevant logic is as follows:
|
|
|
|
* If "skill_type_id", "skill_id", "skill_level_id", self._linked_field_name() are not in vals, this method will
|
|
behave like any standard write method.
|
|
* Otherwise, the current record is archived, by changing valid_to to yesterday, and a new one is created with
|
|
values from vals and self, with vals taking priority.
|
|
|
|
|
|
:param commands: list of WRITE commands
|
|
:return: List of CREATE, WRITE, UNLINK commands
|
|
"""
|
|
self_dict = self.grouped('id')
|
|
result_command = []
|
|
create_vals = []
|
|
remove_from_expire = self.env[self._name]
|
|
|
|
def _get_passive_field_value(field, skill):
|
|
"""
|
|
Extracts the appropriate value from a field to be passed into a vals dict for record creation/writing.
|
|
Returns the raw value for most fields but extracts id(s) for relational fields.
|
|
|
|
:param field: Field name as a string to process
|
|
:param skill: Source record to extract value from
|
|
:return: ORM-ready value for the field
|
|
"""
|
|
field_type = self._fields[field].type
|
|
if field_type == "many2one":
|
|
return skill[field].id
|
|
if field_type == "many2many" or field_type == "one2many":
|
|
return skill[field].ids
|
|
return skill[field]
|
|
|
|
for command in commands:
|
|
ind_skill = self_dict.get(command[1])
|
|
vals = command[2]
|
|
if not any(key in vals for key in ["skill_type_id", "skill_id", "skill_level_id", self._linked_field_name()]):
|
|
result_command.append([1, ind_skill.id, vals])
|
|
remove_from_expire += ind_skill
|
|
continue
|
|
|
|
passive_vals = {
|
|
field: vals.get(field, _get_passive_field_value(field, ind_skill))
|
|
for field in self._get_passive_fields()
|
|
}
|
|
new_vals = {
|
|
f'{self._linked_field_name()}': vals.get(self._linked_field_name(), ind_skill[self._linked_field_name()].id),
|
|
'skill_id': vals.get('skill_id', ind_skill.skill_id.id),
|
|
'skill_level_id': vals.get('skill_level_id', ind_skill.skill_level_id.id),
|
|
'skill_type_id': vals.get('skill_type_id', ind_skill.skill_type_id.id),
|
|
**passive_vals,
|
|
}
|
|
skill_type = self.env['hr.skill.type'].browse(new_vals['skill_type_id'])
|
|
valid_from = vals.get('valid_from', ind_skill.valid_from if skill_type.is_certification else fields.Date.today())
|
|
valid_to = vals.get('valid_to', ind_skill.valid_to if skill_type.is_certification else False)
|
|
new_vals.update({
|
|
'valid_from': valid_from,
|
|
'valid_to': valid_to,
|
|
})
|
|
create_vals.append(new_vals)
|
|
return result_command + (self - remove_from_expire)._expire_individual_skills() + self.env[self._name]._create_individual_skills(create_vals)
|
|
|
|
def _get_transformed_commands(self, commands, individuals):
|
|
"""
|
|
Transform a list of ORM commands to fit with the business constraints and preserve the logic of how skills and
|
|
certifications should behave. The key behaviors are as follows:
|
|
|
|
Skills:
|
|
1. Only one active skill per `skill_id` is allowed (e.g., one "English" skill per linked_field record).
|
|
|
|
Certifications (`is_certification=True`):
|
|
1. Multiple certifications with the same `skill_id` and `level_id` are allowed if their date ranges differ (e.g.,
|
|
"Odoo Certified (2024-01-01 → 2024-12-31)" and "Odoo Certified (2024-06-01 → 2025-05-31)" can coexist.)
|
|
|
|
Shared Rules:
|
|
- Updates always create new records (archiving old ones) rather than in-place writes.
|
|
- No two records can have all their fields identical.
|
|
- A skill/certification is active if `valid_to` is unset or in the future.
|
|
- A skill/certification that is not active is considered archived.
|
|
- A skill/certification is only deleted if valid_from is from the past 24 hours or it is expired.
|
|
|
|
:param commands: list of CREATE, WRITE, and UNLINK commands
|
|
:param individuals: a recordset of linked_field's model
|
|
:return: List of CREATE, WRITE, and UNLINK commands
|
|
"""
|
|
if not commands:
|
|
return
|
|
updated_ids = set()
|
|
updated_commands = []
|
|
created_values = []
|
|
unlinked_ids = set()
|
|
for command in commands:
|
|
if command[0] == 1:
|
|
updated_ids.add(command[1])
|
|
updated_commands.append(command)
|
|
elif command[0] == 2:
|
|
unlinked_ids.add(command[1])
|
|
elif command[0] == 0:
|
|
if individuals:
|
|
for individual in individuals:
|
|
individual_command = command[2]
|
|
individual_command[self._linked_field_name()] = individual.id
|
|
created_values.append(individual_command)
|
|
else:
|
|
created_values.append(command[2])
|
|
mixed_command_ids = list(updated_ids & unlinked_ids)
|
|
if mixed_command_ids:
|
|
# reset updated values
|
|
updated_ids = set()
|
|
updated_commands = []
|
|
for command in commands:
|
|
if command[1] not in mixed_command_ids and command[0] == 1:
|
|
updated_commands.append(command)
|
|
updated_ids.append(command[1])
|
|
# Process individual_skill_ids values
|
|
unlinked_commands = self.env[self._name].browse(list(unlinked_ids))._expire_individual_skills()
|
|
updated_commands = self.env[self._name].browse(list(updated_ids))._write_individual_skills(updated_commands)
|
|
created_commands = self.env[self._name]._create_individual_skills(created_values)
|
|
return unlinked_commands + updated_commands + created_commands
|