update code : promote - approval flow works (need more testing)

This commit is contained in:
hoangvv 2025-01-20 18:36:37 +07:00
parent a6b9292857
commit bb344d53fb
8 changed files with 148 additions and 60 deletions

View File

@ -2,10 +2,11 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import logging
from odoo import api, fields, models, tools, _
from odoo.exceptions import ValidationError
from odoo.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__)
CATEGORY_SELECTION = [
('required', 'Required'),
@ -97,25 +98,28 @@ class ApprovalCategory(models.Model):
model_id = fields.Many2one('ir.model', string='Model', store=True)
model_name = fields.Char(string='Model Name', related='model_id.model', store=True)
invalid_minimum = fields.Boolean("Invalid Minimum", compute="_compute_invalid_minimum", store=True, readonly=False)
invalid_minimum_warning = fields.Boolean("Invalid minimum Warning",compute="_compute_invalid_minimum_warning")
domain = fields.Char(compute='_compute_domain')
# invalid_minimum = fields.Boolean("Invalid Minimum", compute="_compute_invalid_minimum", store=True, readonly=False)
# invalid_minimum_warning = fields.Boolean("Invalid minimum Warning",compute="_compute_invalid_minimum_warning")
# domain = fields.Char(string='Conditions')
# ---------------------------------------- Methods -----------------------------------------
def _compute_domain(self):
for record in self:
record.domain = record.conditions_ids.domain if record.conditions_ids else '[]'
@api.depends('conditions_ids')
def _compute_invalid_minimum(self):
for record in self.filtered('conditions_ids'):
record.invalid_minimum = record.conditions_ids.invalid_minimum
@api.depends('conditions_ids')
def _compute_invalid_minimum_warning(self):
for record in self.filtered('conditions_ids'):
record.invalid_minimum_warning = record.conditions_ids.invalid_minimum_warning
# @api.depends('conditions_ids')
# def _compute_invalid_minimum(self):
# for record in self:
# # If you expect to compute the invalid_minimum for each condition, loop through them
# invalid_minimums = record.conditions_ids.mapped('invalid_minimum')
# if invalid_minimums:
# # Handle the logic for determining invalid_minimum (example)
# record.invalid_minimum = any(invalid_minimums)
# @api.depends('conditions_ids')
# def _compute_invalid_minimum_warning(self):
# for record in self.filtered('conditions_ids'):
# record.invalid_minimum_warning = record.conditions_ids.invalid_minimum_warning
def _compute_request_to_validate_count(self):
domain = [('request_status', '=', 'pending'), ('approver_ids.user_id', '=', self.env.user.id)]
requests_data = self.env['approval.request']._read_group(domain, ['category_id'], ['__count'])
@ -168,7 +172,8 @@ class ApprovalCategory(models.Model):
'company_id': vals.get('company_id'),
})
vals['sequence_id'] = sequence.id
return super().create(vals_list)
res = super().create(vals_list)
return res
def write(self, vals):
if 'sequence_code' in vals:
@ -188,7 +193,9 @@ class ApprovalCategory(models.Model):
for approval_category in self:
if approval_category.sequence_id:
approval_category.sequence_id.company_id = vals.get('company_id')
return super().write(vals)
res = super().write(vals)
return res
def create_request(self):
self.ensure_one()

View File

@ -12,8 +12,9 @@ class ApprovalCategoryApprover(models.Model):
_rec_name = 'user_id'
_order = 'sequence'
sequence = fields.Integer('Sequence', default=10)
category_id = fields.Many2one('approval.category', string='Approval Type', required=True)
company_id = fields.Many2one('res.company', related='category_id.company_id')
category_id = fields.Many2one('approval.category', string='Approval Type')
condition_id = fields.Many2one('approval.category.condition', string='Approval Condition')
company_id = fields.Many2one('res.company', related='condition_id.company_id')
user_id = fields.Many2one('res.users', string='User', ondelete='cascade', required=True,
check_company=True, domain="[('company_ids', 'in', company_id), ('id', 'not in', existing_user_ids)]")
job_title = fields.Char(string='Job Position', compute='_compute_job_title', store=True)
@ -33,7 +34,7 @@ class ApprovalCategoryApprover(models.Model):
record.job_title = employee.job_title if employee else ''
else:
record.job_title = ''
@api.depends('category_id')
@api.depends('condition_id')
def _compute_existing_user_ids(self):
for record in self:
record.existing_user_ids = record.category_id.approver_ids.user_id
record.existing_user_ids = record.condition_id.approver_ids.user_id

View File

@ -8,12 +8,16 @@ class ApprovalConditions(models.Model):
sequence = fields.Integer(default=1)
description = fields.Text(string="Description")
company_id = fields.Many2one(
'res.company', 'Company', copy=False,
required=True, index=True, default=lambda s: s.env.company)
approver_sequence = fields.Boolean('Approvers Sequence?', help="If checked, the approvers have to approve in sequence.")
approval_minimum = fields.Integer(string="Minimum Approval", default=5, required=True)
invalid_minimum = fields.Boolean(compute='_compute_invalid_minimum')
invalid_minimum_warning = fields.Char(compute='_compute_invalid_minimum')
approver_ids = fields.One2many('approval.category.approver', 'category_id', string="Approvers")
category_id = fields.Many2one('approval.category', string="Approval Category", required=True,ondelete="cascade")
approver_ids = fields.One2many('approval.category.approver', 'condition_id', string="Approvers")
user_ids = fields.Many2many('res.users', compute='_compute_user_ids', string="Approver Users")
category_id = fields.Many2one('approval.category', string="Approval Category")
model_id = fields.Many2one('ir.model', string='Model', related='category_id.model_id', store=True, readonly=True)
model_name = fields.Char(
string='Model Name',
@ -21,7 +25,7 @@ class ApprovalConditions(models.Model):
store=True,
readonly=True
)
domain = fields.Char(string="Conditions", compute='_compute_domain', readonly=False, store=True)
domain = fields.Char(string="Conditions", readonly=False, store=True)
# @api.depends('category_id.model_id')
# def _compute_model_id(self):
@ -29,11 +33,10 @@ class ApprovalConditions(models.Model):
# for record in self:
# record.model_id = record.category_id.model_id
@api.depends('model_id')
def _compute_domain(self):
@api.depends('approver_ids')
def _compute_user_ids(self):
for record in self:
record.domain = '[]'
record.user_ids = record.approver_ids.user_id
@api.depends('approval_minimum', 'approver_ids')
def _compute_invalid_minimum(self):
@ -54,6 +57,15 @@ class ApprovalConditions(models.Model):
# for record in self:
# record.filter_id = False
@api.constrains('approver_ids')
def _constrains_approver_ids(self):
# There seems to be a problem with how the database is updated which doesn't let use to an sql constraint for this
# Issue is: records seem to be created before others are saved, meaning that if you originally have only user a
# change user a to user b and add a new line with user a, the second line will be created and will trigger the constraint
# before the first line will be updated which wouldn't trigger a ValidationError
for record in self:
if len(record.approver_ids) != len(record.approver_ids.user_id):
raise ValidationError(_('An user may not be in the approver list multiple times.'))
@api.constrains('approval_minimum', 'approver_ids')
def _constrains_approval_minimum(self):
for record in self:

View File

@ -21,7 +21,7 @@ class ApprovalPromotionLine(models.Model):
current_job = fields.Char(
"Current Job", required=True,
compute="_compute_promotion", store=True, readonly=True, precompute=True)
designation_job = fields.Char(
new_designation = fields.Char(
"New Designation", required=True,
compute="_compute_promotion", store=True, readonly=True, precompute=True)
@api.depends('promote_id')
@ -29,5 +29,4 @@ class ApprovalPromotionLine(models.Model):
for line in self:
line.description = line.promote_id.description or line.promote_id.display_name
line.current_job = line.promote_id.job_id.name or ''
line.designation_job = line.promote_id.designation_id.name or ''
line.new_designation = line.promote_id.designation_id.name or ''

View File

@ -1,9 +1,9 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import api, Command, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__)
class ApprovalRequest(models.Model):
_name = 'approval.request'
_description = 'Approval Request'
@ -68,7 +68,6 @@ class ApprovalRequest(models.Model):
has_location = fields.Selection(related="category_id.has_location")
has_product = fields.Selection(related="category_id.has_product")
has_promotion = fields.Selection(related="category_id.has_promotion")
requirer_document = fields.Selection(related="category_id.requirer_document")
approval_minimum = fields.Integer(related="category_id.approval_minimum")
approval_type = fields.Selection(related="category_id.approval_type")
@ -99,30 +98,81 @@ class ApprovalRequest(models.Model):
for request in self:
if request.date_start and request.date_end and request.date_start > request.date_end:
raise ValidationError(_("Start date should precede the end date."))
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
category = 'category_id' in vals and self.env['approval.category'].browse(vals['category_id'])
if category and category.automated_sequence:
vals['name'] = category.sequence_id.next_by_id()
self._check_conditions_promotion(vals)
created_requests = super().create(vals_list)
self._check_conditions(created_requests)
for request in created_requests:
request.message_subscribe(partner_ids=request.request_owner_id.partner_id.ids)
return created_requests
def _check_conditions_promotion(self,vals):
if vals.get('has_promotion'):
promotion_lines = vals.get('promotion_line_ids', [])
approval_conditions_to_add = []
if promotion_lines:
for promotion_line in promotion_lines:
promotion_line_record = self.env['approval.promotion.line'].browse(promotion_line)
if promotion_line_record and promotion_line_record.promote_id:
def _check_conditions(self,request):
data = request
if isinstance(request, bool):
_logger.error(f"Expected 'vals' to be a list, got {type(request)}")
data = self
for request in data:
if request.has_promotion:
promotion_lines = request.promotion_line_ids
if promotion_lines:
conditions = request.category_id.conditions_ids
for condition in conditions:
satisfy_condition = False
domain = condition.domain if condition.domain else []
return
return
# Ensure domain is in list or tuple format
if isinstance(domain, str):
try:
domain = safe_eval(domain)
except Exception as e:
_logger.error(f"Failed to evaluate domain: {e}")
domain = []
elif not isinstance(domain, (list, tuple)):
domain = []
_logger.debug(f"Evaluating condition: {condition}, Domain: {domain}")
for promotion_line in promotion_lines:
if not promotion_line.promote_id or not promotion_line.promote_id.employee_id:
_logger.warning(f"No promote_id or employee_id found for promotion_line: {promotion_line.id}")
continue
model_name = request.category_id.model_name
# if not hasattr(self.env, model_name):
# _logger.error(f"Invalid model name: {model_name}")
# continue
result = self.env[model_name].search(domain)
# Compare promotion_line with result
for res in result:
if promotion_line.promote_id.employee_id.id == res.id:
satisfy_condition = True
break
if satisfy_condition:
request._add_approvers(condition)
else:
_logger.info('Condition not satisfied')
def _add_approvers(self, condition):
for request in self:
approvers = condition.approver_ids
for approver in approvers:
existing_approver = request.approver_ids.filtered(lambda x: x.user_id == approver.user_id)
if existing_approver:
existing_approver.write({
'status': 'pending',
})
else:
self.env['approval.approver'].create({
'user_id': approver.user_id.id,
'request_id': request.id,
'required':approver.required,
'status': 'pending'
})
@api.ondelete(at_uninstall=False)
def unlink_attachments(self):
attachment_ids = self.env['ir.attachment'].search([
@ -168,7 +218,6 @@ class ApprovalRequest(models.Model):
approvers = approvers[0] if approvers and approvers[0].status != 'pending' else self.env['approval.approver']
else:
approvers = approvers.filtered(lambda a: a.status == 'new')
approvers._create_activity()
approvers.sudo().write({'status': 'pending'})
self.sudo().write({'date_confirmed': fields.Datetime.now()})
@ -230,7 +279,17 @@ class ApprovalRequest(models.Model):
subject=subject,
partner_ids=approval.request_owner_id.partner_id.ids,
)
if self.has_promotion:
all_confirm = True
for approver in self.approver_ids:
if approver.required and approver.status != 'approved':
all_confirm = False
if all_confirm:
for promote in self.promotion_line_ids:
promote_records = self.env['hr.promote'].search([('id', '=', promote.promote_id.id)],limit=1)
promote_records.write({
'state': 'confirmed'
})
self.sudo()._update_next_approvers('pending', approver, only_next_approver=True)
self.sudo()._get_user_approval_activities(user=self.env.user).action_feedback()
@ -360,9 +419,8 @@ class ApprovalRequest(models.Model):
if 'request_owner_id' in vals:
for approval in self:
approval.message_unsubscribe(partner_ids=approval.request_owner_id.partner_id.ids)
res = super().write(vals)
self._check_conditions(res)
if 'request_owner_id' in vals:
for approval in self:
approval.message_subscribe(partner_ids=approval.request_owner_id.partner_id.ids)
@ -417,7 +475,6 @@ class ApprovalApprover(models.Model):
category_approver = fields.Boolean(compute='_compute_category_approver')
can_edit = fields.Boolean(compute='_compute_can_edit')
can_edit_user_id = fields.Boolean(compute='_compute_can_edit', help="Simple users should not be able to remove themselves as approvers because they will lose access to the record if they misclick.")
def action_approve(self):
self.request_id.action_approve(self)

View File

@ -79,7 +79,7 @@
<notebook>
<page string="Conditions">
<group>
<field name="conditions_ids" nolabel="1"/>
<field name="conditions_ids" widget="one2many_list" nolabel="1"/>
</group>
</page>
<page string="Options">

View File

@ -8,7 +8,7 @@
<field name="company_id" column_invisible="True"/>
<field name="promote_id"/>
<field name="current_job" />
<field name="designation_job"/>
<field name="new_designation"/>
<field name="description"/>
</list>
</field>
@ -25,7 +25,7 @@
<field name="promote_id"/>
<field name="description"/>
<!-- <field name="current_job" options="{'no_create': True, 'no_open': True}"/>
<field name="designation_job" options="{'no_create': True, 'no_open': True}"/> -->
<field name="new_designation" options="{'no_create': True, 'no_open': True}"/> -->
</group>
</sheet>
</form>
@ -41,7 +41,7 @@
<templates>
<t t-name="card" class="d-flex justify-content-between">
<field name="promote_id" class="fw-bolder fs-5"/>
<field name="designation_job" class="fw-bolder fs-5"/>
<field name="new_designation" class="fw-bolder fs-5"/>
</t>
</templates>
</kanban>

View File

@ -103,8 +103,20 @@ class HRPromote(models.Model):
raise exceptions.ValidationError("The employee does not have a current job position.")
promotion_record = super(HRPromote, self).create(vals)
promotion_records.append(promotion_record)
# Create an Approval request
category = self.env['approval.category'].search([("has_promotion", "=", "required")], limit=1)
if not category:
category = self.env['approval.category'].search([], limit=1)
# Create the approval request
self.env['approval.request'].create({
'category_id': category.id,
'date': fields.Datetime.now(),
'company_id': self.env.company.id,
'name': 'Automatic',
'promotion_line_ids': [(0, 0, {
'company_id': self.env.company.id,
'promote_id': promotion_record.id,
})],
})
return self.browse([record.id for record in promotion_records])
def action_save_and_close(self):
return {