update approval module (WIP)

This commit is contained in:
hoangvv 2025-01-17 12:08:27 +07:00
parent b8024171a2
commit 16ca87bc01
9 changed files with 248 additions and 47 deletions

View File

@ -33,6 +33,7 @@ creates next activities for the related approvers.
'report/approval_request_report.xml', 'report/approval_request_report.xml',
'views/approval_request_views.xml', 'views/approval_request_views.xml',
'views/res_users_views.xml', 'views/res_users_views.xml',
'views/approval_condition_views.xml'
], ],
'demo':[ 'demo':[
'data/approval_demo.xml', 'data/approval_demo.xml',

View File

@ -7,3 +7,4 @@ from . import approval_product_line
from . import approval_request from . import approval_request
from . import mail_activity from . import mail_activity
from . import ir_attachment from . import ir_attachment
from . import approval_condition

View File

@ -95,6 +95,8 @@ class ApprovalCategory(models.Model):
copy=False, check_company=True) copy=False, check_company=True)
model_id = fields.Many2one('ir.model', 'Model',copy=False) model_id = fields.Many2one('ir.model', 'Model',copy=False)
conditions_ids = fields.Many2many('approval.condition', 'approval_category_condition_rel', 'category_id', 'condition_id')
# ---------------------------------------- Methods ----------------------------------------- # ---------------------------------------- Methods -----------------------------------------
def _compute_request_to_validate_count(self): def _compute_request_to_validate_count(self):

View File

@ -0,0 +1,77 @@
from odoo import api, fields, models
class ApprovalConditions(models.Model):
_name = 'approval.condition'
_description = 'Approval Condition'
_order = 'sequence, id'
name = fields.Char(string="Name", required=True)
description = fields.Text(string="Description", required=True)
filter_pre_condition = fields.Char(
string='Condition',
compute='_compute_filter_pre_condition',
readonly=False, store=True,
help="If present, this condition must be satisfied before the update of the record. "
"Not checked on record creation.")
trigger = fields.Selection(
[
('on_stage_set', "Stage is set to"),
('on_user_set', "User is set"),
('on_tag_set', "Tag is added"),
('on_state_set', "State is set to"),
('on_priority_set', "Priority is set to"),
('on_archive', "On archived"),
('on_unarchive', "On unarchived"),
('on_create_or_write', "On save"),
('on_create', "On creation"), # deprecated, use 'on_create_or_write' instead
('on_write', "On update"), # deprecated, use 'on_create_or_write' instead
('on_unlink', "On deletion"),
('on_change', "On UI change"),
('on_time', "Based on date field"),
('on_time_created', "After creation"),
('on_time_updated', "After last update"),
("on_message_received", "On incoming message"),
("on_message_sent", "On outgoing message"),
('on_webhook', "On webhook"),
], string='Trigger',
compute='_compute_trigger', readonly=False, store=True, required=True
)
trigger_field_ids = fields.Many2many(
'ir.model.fields', string='Trigger Fields',
compute='_compute_trigger_field_ids', readonly=False, store=True,
help="The automation rule will be triggered if and only if one of these fields is updated."
"If empty, all fields are watched.")
trg_field_ref = fields.Reference(
selection=[('ir.model.fields', 'Field Reference')],
string='Trigger Reference',
readonly=False,
store=True,
help="Some triggers need a reference to another field. This field is used to store it."
)
sequence = fields.Boolean('Approvers Sequence?', help="If checked, the approvers have to approve in sequence.")
approval_minimum = fields.Integer(string="Minimum Approval", default=1, required=True)
user_ids = fields.Many2many('res.users', compute='_compute_user_ids', string="Approver Users")
approver_ids = fields.One2many('approval.category.approver', 'category_id', string="Approvers")
@api.depends('approver_ids')
def _compute_user_ids(self):
for record in self:
record.user_ids = record.approver_ids.user_id
@api.depends('trigger', 'trigger_field_ids', 'trg_field_ref')
def _compute_filter_pre_condition(self):
for automation in self:
if automation.trigger != 'on_tag_set' or len(automation.trigger_field_ids) != 1:
automation.filter_pre_condition = False
else:
field = automation.trigger_field_ids.name
value = automation.trg_field_ref
automation.filter_pre_condition = f"[('{field}', 'not in', [{value}])]" if value else False
def delete_condition(self):
for record in self:
record.unlink()

View File

@ -13,3 +13,5 @@ access_approval_approver_user,access_approval_approver,model_approval_approver,g
access_approval_approver_manager,access_approval_approver,model_approval_approver,group_approval_manager,1,1,1,1 access_approval_approver_manager,access_approval_approver,model_approval_approver,group_approval_manager,1,1,1,1
access_approval_product_line_user,access_approval_product_line,model_approval_product_line,base.group_user,1,1,1,1 access_approval_product_line_user,access_approval_product_line,model_approval_product_line,base.group_user,1,1,1,1
access_approval_product_line_manager,access_approval_product_line,model_approval_product_line,group_approval_manager,1,1,1,1 access_approval_product_line_manager,access_approval_product_line,model_approval_product_line,group_approval_manager,1,1,1,1
access_approval_condition,access_approval_condition,model_approval_condition,base.group_user,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
13 access_approval_approver_manager access_approval_approver model_approval_approver group_approval_manager 1 1 1 1
14 access_approval_product_line_user access_approval_product_line model_approval_product_line base.group_user 1 1 1 1
15 access_approval_product_line_manager access_approval_product_line model_approval_product_line group_approval_manager 1 1 1 1
16 access_approval_condition access_approval_condition model_approval_condition base.group_user 1 1 1 1
17

View File

@ -0,0 +1,52 @@
.o_approvals_category_actions_field,
.o_approvals_category_kanban_view {
.o_kanban_ungrouped {
padding: 0;
.o_kanban_record {
width: 30%;
margin: 0;
&.o_kanban_ghost {
display: none;
}
}
}
}
.o_approvals_category_actions_field .o_kanban_ghost {
display: none;
}
.o_approvals_category_kanban_view {
.o_kanban_grouped .row {
flex-direction: column !important;
gap: 0.5rem !important;
> * {
width: 100% !important;
> * {
margin: 0 0.5rem !important;
}
}
}
.o_kanban_ungrouped .o_kanban_record .oe_kanban_global_click {
border-top: 0;
display: flex;
align-items: center;
.o_widget_web_ribbon {
align-self: flex-start;
}
}
}
.o_form_view .o_group .o_field_widget, .o_form_view .o_inner_group .o_field_widget {
width: 50%;
}
.o_approvals_category_error {
pre {
max-height: 25vh !important;
}
}

View File

@ -76,39 +76,54 @@
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/> <field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</group> </group>
<notebook> <notebook>
<page string="Coditions" name="conditions"> <page string="Options">
<group> <!-- <group string="Fields" name="option_settings">
<!-- <group string="Fields" name="option_settings"> <field name="active" invisible="1"/>
<field name="active" invisible="1"/> <field name="requirer_document" string="Document" widget="radio" options="{'horizontal': true}"/>
<field name="requirer_document" string="Document" widget="radio" options="{'horizontal': true}"/> <field name="has_partner" string="Contact" widget="radio" options="{'horizontal': true}"/>
<field name="has_partner" string="Contact" widget="radio" options="{'horizontal': true}"/> <field name="has_date" string="Date" widget="radio" options="{'horizontal': true}"/>
<field name="has_date" string="Date" widget="radio" options="{'horizontal': true}"/> <field name="has_period" string="Period" widget="radio" options="{'horizontal': true}"/>
<field name="has_period" string="Period" widget="radio" options="{'horizontal': true}"/> <field name="has_product" string="Product" force_save="1" widget="radio" options="{'horizontal': true}"/>
<field name="has_product" string="Product" force_save="1" <field name="has_quantity" string="Quantity" widget="radio" options="{'horizontal': true}"/>
widget="radio" options="{'horizontal': true}"/> <field name="has_amount" string="Amount" widget="radio" options="{'horizontal': true}"/>
<field name="has_quantity" string="Quantity" widget="radio" options="{'horizontal': true}"/> <field name="has_reference" string="Reference" widget="radio" options="{'horizontal': true}"/>
<field name="has_amount" string="Amount" widget="radio" options="{'horizontal': true}"/> <field name="has_payment_method" string="Payment" widget="radio" options="{'horizontal': true}" invisible="1"/>
<field name="has_reference" string="Reference" widget="radio" options="{'horizontal': true}"/> <field name="has_location" string="Location" widget="radio" options="{'horizontal': true}"/>
<field name="has_payment_method" string="Payment" widget="radio" options="{'horizontal': true}" invisible="1"/> </group> -->
<field name="has_location" string="Location" widget="radio" options="{'horizontal': true}"/>
</group> -->
<!-- <group string="Approvers" name="approvers">
<field name="manager_approval"/>
<separator colspan="2"/>
<field name="approver_ids"/>
<field name="approver_sequence" invisible="approval_minimum == 0"/>
<field name="approval_minimum"/>
<field name="invalid_minimum" invisible="1"/>
<div class="text-warning" colspan="2" invisible="not invalid_minimum">
<span class="fa fa-warning" title="Invalid minimum approvals"/><field name="invalid_minimum_warning" nolabel="1"/>
</div>
</group> -->
<!-- <group string="Approvers" name="approvers">
<field name="manager_approval"/>
<separator colspan="2"/>
<field name="approver_ids"/>
<field name="approver_sequence" invisible="approval_minimum == 0"/>
<field name="approval_minimum"/>
<field name="invalid_minimum" invisible="1"/>
<div class="text-warning" colspan="2" invisible="not invalid_minimum">
<span class="fa fa-warning" title="Invalid minimum approvals"/><field name="invalid_minimum_warning" nolabel="1"/>
</div>
</group> -->
<group>
<field name="conditions_ids"
nolabel="1"
widget="many2many"
class="o_approvals_category_actions_field"
context="{'default_model_id': model_id, 'form_view_ref': 'approvals.approval_condition_view_form', 'list_view_ref': 'approvals.approval_condition_view_list'}"
width="20%">
<kanban>
<control>
<create string="Add Condition" />
</control>
<templates>
<t t-name="card" class="flex-row align-items-center gap-1">
<field name="sequence" widget="handle" class="px-1" width="10%" />
<field name="name" class="text-truncate" />
<button type="delete" name="delete" class="btn fa fa-trash fa-xl px-3 ms-auto" title="Delete Condition" />
</t>
</templates>
</kanban>
</field>
</group> </group>
</page> </page>
<page string="Code">
<field name="code" widget="code" options="{'mode': 'python'}" placeholder="Enter Python code here. Help about Python expression is available in the help tab of this document."/>
</page>
</notebook> </notebook>
</sheet> </sheet>
</form> </form>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="approval_condition_view_form" model="ir.ui.view">
<field name="name">approval.condition.form</field>
<field name="model">approval.condition</field>
<field name="arch" type="xml">
<form>
<sheet>
<group string="Conditions" name="conditions">
<!-- <field name="filter_pre_condition" widget="domain" groups="base.group_no_one"
options="{'model': 'approval.condition', 'in_dialog': True}"
invisible="trigger in ['on_webhook', 'on_time', 'on_time_created', 'on_time_updated']" /> -->
</group>
<group string="Approvers" name="approvers">
<field name="approver_ids" widget="many2many_tags">
<list>
<field name="name"/>
<field name="job_title"/>
</list>
</field>
<field name="name"/>
<field name="sequence"/>
<field name="approval_minimum"/>
<field name="description"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="approval_condition_view_list" model="ir.ui.view">
<field name="name">approval.condition.list</field>
<field name="model">approval.condition</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
<field name="description"/>
<field name="sequence" class="text-truncate" />
<field name="approval_minimum" class="text-truncate" />
<button name="delete_condition" type="object" class="btn fa fa-trash fa-xl px-3 ms-auto" title="Delete Condition" />
</list>
</field>
</record>
</odoo>

View File

@ -1,5 +1,5 @@
from odoo.exceptions import ValidationError,UserError from odoo.exceptions import ValidationError,UserError
from odoo import api, fields, models from odoo import api, fields, models, exceptions
from datetime import date from datetime import date
class HRPromote(models.Model): class HRPromote(models.Model):
# ----------------------------------- Private Attributes --------------------------------- # ----------------------------------- Private Attributes ---------------------------------
@ -71,23 +71,30 @@ class HRPromote(models.Model):
else: else:
record.name = "Promotion" record.name = "Promotion"
@api.model @api.model
def create(self, vals): def create(self, vals_list):
vals['state'] = 'approval_needed' # Ensure vals_list is always a list
if 'employee_id' in vals and not vals.get('job_id'): if isinstance(vals_list, dict):
employee = self.env['hr.employee'].browse(vals['employee_id']) vals_list = [vals_list]
vals['job_id'] = employee.job_id.id if employee.job_id else False
if not vals.get('job_id'): promotion_records = []
raise ValueError("The employee does not have a current job position.") for vals in vals_list:
promotion_record = super().create(vals) # Set default state
# Create the approval request related to the promotion vals['state'] = 'approval_needed'
self.env['approval.request'].create({
'employee_id': vals['employee_id'], # Handle employee's job_id logic
'promotion_record_id': promotion_record.id, if 'employee_id' in vals and not vals.get('job_id'):
'state': 'draft', # You can adjust this based on your workflow employee = self.env['hr.employee'].browse(vals['employee_id'])
'approval_type': 'promotion', # Custom field for the type of approval vals['job_id'] = employee.job_id.id if employee.job_id else False
})
return promotion_record # Validate job_id existence
if not vals.get('job_id'):
raise exceptions.ValidationError("The employee does not have a current job position.")
# Create the promotion record
promotion_record = super(HRPromote, self).create(vals)
promotion_records.append(promotion_record)
return self.browse([record.id for record in promotion_records])
def action_save_and_close(self): def action_save_and_close(self):
return { return {
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',