1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/approvals/models/approval_request.py
2024-12-10 09:04:09 +07:00

389 lines
19 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, Command, fields, models, _
from odoo.exceptions import UserError, ValidationError
from collections import defaultdict
class ApprovalRequest(models.Model):
_name = 'approval.request'
_description = 'Approval Request'
_inherit = ['mail.thread.main.attachment', 'mail.activity.mixin']
_order = 'name'
_mail_post_access = 'read'
_check_company_auto = True
@api.model
def _read_group_request_status(self, stages, domain, order):
request_status_list = dict(self._fields['request_status'].selection).keys()
return request_status_list
name = fields.Char(string="Approval Subject", tracking=True)
category_id = fields.Many2one('approval.category', string="Category", required=True)
category_image = fields.Binary(related='category_id.image')
approver_ids = fields.One2many('approval.approver', 'request_id', string="Approvers", check_company=True,
compute='_compute_approver_ids', store=True, readonly=False)
company_id = fields.Many2one(
string='Company', related='category_id.company_id',
store=True, readonly=True, index=True)
date = fields.Datetime(string="Date")
date_start = fields.Datetime(string="Date start")
date_end = fields.Datetime(string="Date end")
quantity = fields.Float(string="Quantity")
location = fields.Char(string="Location")
date_confirmed = fields.Datetime(string="Date Confirmed")
partner_id = fields.Many2one('res.partner', string="Contact", check_company=True)
reference = fields.Char(string="Reference")
amount = fields.Float(string="Amount")
reason = fields.Html(string="Description")
request_status = fields.Selection([
('new', 'To Submit'),
('pending', 'Submitted'),
('approved', 'Approved'),
('refused', 'Refused'),
('cancel', 'Cancel'),
], default="new", compute="_compute_request_status",
store=True, index=True, tracking=True,
group_expand='_read_group_request_status')
request_owner_id = fields.Many2one('res.users', string="Request Owner",
check_company=True, domain="[('company_ids', 'in', company_id)]")
user_status = fields.Selection([
('new', 'New'),
('pending', 'To Approve'),
('waiting', 'Waiting'),
('approved', 'Approved'),
('refused', 'Refused'),
('cancel', 'Cancel')], compute="_compute_user_status")
has_access_to_request = fields.Boolean(string="Has Access To Request", compute="_compute_has_access_to_request")
change_request_owner = fields.Boolean(string='Can Change Request Owner', compute='_compute_has_access_to_request')
attachment_number = fields.Integer('Number of Attachments', compute='_compute_attachment_number')
product_line_ids = fields.One2many('approval.product.line', 'approval_request_id', check_company=True)
has_date = fields.Selection(related="category_id.has_date")
has_period = fields.Selection(related="category_id.has_period")
has_quantity = fields.Selection(related="category_id.has_quantity")
has_amount = fields.Selection(related="category_id.has_amount")
has_reference = fields.Selection(related="category_id.has_reference")
has_partner = fields.Selection(related="category_id.has_partner")
has_payment_method = fields.Selection(related="category_id.has_payment_method")
has_location = fields.Selection(related="category_id.has_location")
has_product = fields.Selection(related="category_id.has_product")
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")
approver_sequence = fields.Boolean(related="category_id.approver_sequence")
automated_sequence = fields.Boolean(related="category_id.automated_sequence")
@api.depends('request_owner_id')
@api.depends_context('uid')
def _compute_has_access_to_request(self):
is_approval_user = self.env.user.has_group('approvals.group_approval_user')
self.change_request_owner = is_approval_user
for request in self:
request.has_access_to_request = request.request_owner_id == self.env.user and is_approval_user
def _compute_attachment_number(self):
domain = [('res_model', '=', 'approval.request'), ('res_id', 'in', self.ids)]
attachment_data = self.env['ir.attachment']._read_group(domain, ['res_id'], ['__count'])
attachment = dict(attachment_data)
for request in self:
request.attachment_number = attachment.get(request.id, 0)
@api.constrains('date_start', 'date_end')
def _check_dates(self):
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()
return super().create(vals_list)
@api.ondelete(at_uninstall=False)
def unlink_attachments(self):
attachment_ids = self.env['ir.attachment'].search([
('res_model', '=', 'approval.request'),
('res_id', 'in', self.ids),
])
if attachment_ids:
attachment_ids.unlink()
def unlink(self):
self.filtered(lambda a: a.has_product).product_line_ids.unlink()
return super().unlink()
def action_get_attachment_view(self):
self.ensure_one()
res = self.env['ir.actions.act_window']._for_xml_id('base.action_attachment')
res['domain'] = [('res_model', '=', 'approval.request'), ('res_id', 'in', self.ids)]
res['context'] = {'default_res_model': 'approval.request', 'default_res_id': self.id}
return res
def action_confirm(self):
# make sure that the manager is present in the list if he is required
self.ensure_one()
if self.category_id.manager_approval == 'required':
employee = self.env['hr.employee'].search([('user_id', '=', self.request_owner_id.id)], limit=1)
if not employee.parent_id:
raise UserError(_('This request needs to be approved by your manager. There is no manager linked to your employee profile.'))
if not employee.parent_id.user_id:
raise UserError(_('This request needs to be approved by your manager. There is no user linked to your manager.'))
if not self.approver_ids.filtered(lambda a: a.user_id.id == employee.parent_id.user_id.id):
raise UserError(_('This request needs to be approved by your manager. Your manager is not in the approvers list.'))
if len(self.approver_ids) < self.approval_minimum:
raise UserError(_("You have to add at least %s approvers to confirm your request.", self.approval_minimum))
if self.requirer_document == 'required' and not self.attachment_number:
raise UserError(_("You have to attach at least one document."))
approvers = self.approver_ids
if self.approver_sequence:
approvers = approvers.filtered(lambda a: a.status in ['new', 'pending', 'waiting'])
approvers[1:].sudo().write({'status': 'waiting'})
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()})
def _get_user_approval_activities(self, user):
domain = [
('res_model', '=', 'approval.request'),
('res_id', 'in', self.ids),
('activity_type_id', '=', self.env.ref('approvals.mail_activity_data_approval').id),
('user_id', '=', user.id)
]
activities = self.env['mail.activity'].search(domain)
return activities
def _ensure_can_approve(self):
if any(approval.approver_sequence and approval.user_status == 'waiting' for approval in self):
raise ValidationError(_('You cannot approve before the previous approver.'))
def _update_next_approvers(self, new_status, approver, only_next_approver, cancel_activities=False):
approvers_updated = self.env['approval.approver']
for approval in self.filtered('approver_sequence'):
current_approver = approval.approver_ids & approver
approvers_to_update = approval.approver_ids.filtered(lambda a: a.status not in ['approved', 'refused'] and (a.sequence > current_approver.sequence or (a.sequence == current_approver.sequence and a.id > current_approver.id)))
if only_next_approver and approvers_to_update:
approvers_to_update = approvers_to_update[0]
approvers_updated |= approvers_to_update
approvers_updated.sudo().status = new_status
if new_status == 'pending':
approvers_updated._create_activity()
if cancel_activities:
approvers_updated.request_id._cancel_activities()
def _cancel_activities(self):
approval_activity = self.env.ref('approvals.mail_activity_data_approval')
activities = self.activity_ids.filtered(lambda a: a.activity_type_id == approval_activity)
activities.unlink()
def action_approve(self, approver=None):
self._ensure_can_approve()
if not isinstance(approver, models.BaseModel):
approver = self.mapped('approver_ids').filtered(
lambda approver: approver.user_id == self.env.user
)
approver.write({'status': 'approved'})
self.sudo()._update_next_approvers('pending', approver, only_next_approver=True)
self.sudo()._get_user_approval_activities(user=self.env.user).action_feedback()
def action_refuse(self, approver=None):
if not isinstance(approver, models.BaseModel):
approver = self.mapped('approver_ids').filtered(
lambda approver: approver.user_id == self.env.user
)
approver.write({'status': 'refused'})
self.sudo()._update_next_approvers('refused', approver, only_next_approver=False, cancel_activities=True)
self.sudo()._get_user_approval_activities(user=self.env.user).action_feedback()
def action_withdraw(self, approver=None):
if not isinstance(approver, models.BaseModel):
approver = self.mapped('approver_ids').filtered(
lambda approver: approver.user_id == self.env.user
)
self.sudo()._update_next_approvers('waiting', approver, only_next_approver=False, cancel_activities=True)
approver.write({'status': 'pending'})
def action_draft(self):
self.mapped('approver_ids').write({'status': 'new'})
def action_cancel(self):
self.sudo()._get_user_approval_activities(user=self.env.user).unlink()
self.mapped('approver_ids').write({'status': 'cancel'})
@api.depends_context('uid')
@api.depends('approver_ids.status')
def _compute_user_status(self):
for approval in self:
approval.user_status = approval.approver_ids.filtered(lambda approver: approver.user_id == self.env.user).status
@api.depends('approver_ids.status', 'approver_ids.required')
def _compute_request_status(self):
for request in self:
status_lst = request.mapped('approver_ids.status')
required_approved = all(a.status == 'approved' for a in request.approver_ids.filtered('required'))
minimal_approver = request.approval_minimum if len(status_lst) >= request.approval_minimum else len(status_lst)
if status_lst:
if status_lst.count('cancel'):
status = 'cancel'
elif status_lst.count('refused'):
status = 'refused'
elif status_lst.count('new'):
status = 'new'
elif status_lst.count('approved') >= minimal_approver and required_approved:
status = 'approved'
else:
status = 'pending'
else:
status = 'new'
request.request_status = status
self.filtered_domain([('request_status', 'in', ['approved', 'refused', 'cancel'])])._cancel_activities()
@api.model
def _update_approver_vals(self, approver_id_vals, approver, new_required, new_sequence):
if approver.required != new_required or approver.sequence != new_sequence:
approver_id_vals.append(Command.update(approver.id, {'required': new_required, 'sequence': new_sequence}))
@api.model
def _create_or_update_approver(self, user_id, users_to_approver, approver_id_vals, required, sequence):
if user_id not in users_to_approver.keys():
approver_id_vals.append(Command.create({
'user_id': user_id,
'status': 'new',
'required': required,
'sequence': sequence,
}))
else:
current_approver = users_to_approver.pop(user_id)
self._update_approver_vals(approver_id_vals, current_approver, required, sequence)
@api.depends('category_id', 'request_owner_id')
def _compute_approver_ids(self):
for request in self:
users_to_approver = {}
for approver in request.approver_ids:
users_to_approver[approver.user_id.id] = approver
users_to_category_approver = {}
for approver in request.category_id.approver_ids:
users_to_category_approver[approver.user_id.id] = approver
approver_id_vals = []
if request.category_id.manager_approval:
employee = self.env['hr.employee'].search([('user_id', '=', request.request_owner_id.id)], limit=1)
if employee.parent_id.user_id:
manager_user_id = employee.parent_id.user_id.id
manager_required = request.category_id.manager_approval == 'required'
# We set the manager sequence to be lower than all others (9) so they are the first to approve.
self._create_or_update_approver(manager_user_id, users_to_approver, approver_id_vals, manager_required, 9)
if manager_user_id in users_to_category_approver.keys():
users_to_category_approver.pop(manager_user_id)
for user_id in users_to_category_approver:
self._create_or_update_approver(user_id, users_to_approver, approver_id_vals,
users_to_category_approver[user_id].required,
users_to_category_approver[user_id].sequence)
for current_approver in users_to_approver.values():
# Reset sequence and required for the remaining approvers that are no (longer) part of the category approvers or managers.
# Set the sequence of these manually added approvers to 1000, so that they always appear after the category approvers.
self._update_approver_vals(approver_id_vals, current_approver, False, 1000)
request.update({'approver_ids': approver_id_vals})
def write(self, vals):
res = super().write(vals)
if 'approver_ids' in vals:
to_resequence = self.filtered_domain([('approver_sequence', '=', True), ('request_status', '=', 'pending')])
for approval in to_resequence:
if not approval.approver_ids.filtered(lambda a: a.status == 'pending'):
approver = approval.approver_ids.filtered(lambda a: a.status == 'waiting')
if approver:
approver[0].status = 'pending'
approver[0]._create_activity()
return res
@api.constrains('approver_ids')
def _check_approver_ids(self):
for request in self:
# make sure the approver_ids are unique per request
if len(request.approver_ids) != len(request.approver_ids.user_id):
raise UserError(_("You cannot assign the same approver multiple times on the same request."))
class ApprovalApprover(models.Model):
_name = 'approval.approver'
_description = 'Approver'
_order = 'sequence, id'
_check_company_auto = True
sequence = fields.Integer('Sequence', default=10)
user_id = fields.Many2one('res.users', string="User", required=True, check_company=True, domain="[('id', 'not in', existing_request_user_ids)]")
existing_request_user_ids = fields.Many2many('res.users', compute='_compute_existing_request_user_ids')
status = fields.Selection([
('new', 'New'),
('pending', 'To Approve'),
('waiting', 'Waiting'),
('approved', 'Approved'),
('refused', 'Refused'),
('cancel', 'Cancel')], string="Status", default="new", readonly=True)
request_id = fields.Many2one('approval.request', string="Request",
ondelete='cascade', check_company=True)
company_id = fields.Many2one(
string='Company', related='request_id.company_id',
store=True, readonly=True, index=True)
required = fields.Boolean(default=False, readonly=True)
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)
def action_refuse(self):
self.request_id.action_refuse(self)
def _create_activity(self):
for approver in self:
approver.request_id.activity_schedule(
'approvals.mail_activity_data_approval',
user_id=approver.user_id.id)
@api.depends('request_id.request_owner_id', 'request_id.approver_ids.user_id')
def _compute_existing_request_user_ids(self):
for approver in self:
approver.existing_request_user_ids = \
self.mapped('request_id.approver_ids.user_id')._origin \
| self.request_id.request_owner_id._origin
@api.depends('category_approver', 'user_id')
def _compute_category_approver(self):
for approval in self:
approval.category_approver = approval.user_id in approval.request_id.category_id.approver_ids.user_id
@api.depends_context('uid')
@api.depends('user_id', 'category_approver')
def _compute_can_edit(self):
is_user = self.env.user.has_group('approvals.group_approval_user')
for approval in self:
approval.can_edit = not approval.user_id or not approval.category_approver or is_user
approval.can_edit_user_id = is_user or approval.request_id.request_owner_id == self.env.user or not approval.user_id