update : add promotion option when creating approval types, fix module dependecy
in manifest (hr_promote,approvals)
This commit is contained in:
parent
16ca87bc01
commit
a6b9292857
@ -16,24 +16,23 @@ procurements, contract approval, etc.
|
||||
According to the approval type configuration, a request
|
||||
creates next activities for the related approvers.
|
||||
""",
|
||||
'depends': ['mail', 'hr', 'product'],
|
||||
'depends': ['mail', 'hr', 'product','hr_promote'],
|
||||
'data': [
|
||||
'security/approval_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
|
||||
'data/approval_category_data.xml',
|
||||
'data/mail_activity_type_data.xml',
|
||||
'data/mail_message_subtype_data.xml',
|
||||
|
||||
'views/approval_category_views.xml',
|
||||
'views/approval_category_approver_views.xml',
|
||||
'views/approval_product_line_views.xml',
|
||||
'views/approval_products_views.xml',
|
||||
'views/approval_promotion_line_views.xml',
|
||||
'views/approval_request_template.xml',
|
||||
'report/approval_request_report.xml',
|
||||
'views/approval_request_views.xml',
|
||||
'views/res_users_views.xml',
|
||||
'views/approval_condition_views.xml'
|
||||
'views/approval_condition_views.xml',
|
||||
],
|
||||
'demo':[
|
||||
'data/approval_demo.xml',
|
||||
|
@ -135,5 +135,20 @@
|
||||
<field name="requirer_document">optional</field>
|
||||
<field name="approval_minimum">1</field>
|
||||
</record>
|
||||
<record id="approval_category_data_promote" model="approval.category">
|
||||
<field name="name">Promote</field>
|
||||
<field name="image" type="base64" file="approvals/static/src/img/Folder.png"/>
|
||||
<field name="sequence">80</field>
|
||||
<field name="has_date">no</field>
|
||||
<field name="has_period">required</field>
|
||||
<field name="has_product">no</field>
|
||||
<field name="has_quantity">no</field>
|
||||
<field name="has_amount">no</field>
|
||||
<field name="has_reference">optional</field>
|
||||
<field name="has_partner">no</field>
|
||||
<field name="has_payment_method">no</field>
|
||||
<field name="has_location">required</field>
|
||||
<field name="requirer_document">optional</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
@ -7,4 +7,5 @@ from . import approval_product_line
|
||||
from . import approval_request
|
||||
from . import mail_activity
|
||||
from . import ir_attachment
|
||||
from . import approval_condition
|
||||
from . import approval_category_condition
|
||||
from . import approval_promotion_line
|
@ -59,10 +59,15 @@ class ApprovalCategory(models.Model):
|
||||
has_product = fields.Selection(
|
||||
CATEGORY_SELECTION, string="Has Product", default="no", required=True,
|
||||
help="Additional products that should be specified on the request.")
|
||||
has_promotion = fields.Selection(
|
||||
CATEGORY_SELECTION, string="Has Promotion", default="no", required=True,
|
||||
help="Additional promotion that should be specified on the request.")
|
||||
|
||||
|
||||
requirer_document = fields.Selection([('required', 'Required'), ('optional', 'Optional')], string="Documents", default="optional", required=True)
|
||||
approval_minimum = fields.Integer(string="Minimum Approval", default="1", required=True)
|
||||
invalid_minimum = fields.Boolean(compute='_compute_invalid_minimum')
|
||||
invalid_minimum_warning = fields.Char(compute='_compute_invalid_minimum')
|
||||
# invalid_minimum = fields.Boolean(compute='_compute_invalid_minimum')
|
||||
# invalid_minimum_warning = fields.Char(compute='_compute_invalid_minimum')
|
||||
approval_type = fields.Selection(string="Approval Type", selection=[],
|
||||
help="Allows you to define which documents you would like to create once the request has been approved")
|
||||
manager_approval = fields.Selection([('approver', 'Is Approver'), ('required', 'Is Required Approver')],
|
||||
@ -74,16 +79,12 @@ class ApprovalCategory(models.Model):
|
||||
Is Required Approver: the employee's manager will be required to approve the request.
|
||||
""")
|
||||
|
||||
# Python code
|
||||
code = fields.Text(string='Python Code', groups='base.group_system',
|
||||
default=DEFAULT_PYTHON_CODE,
|
||||
help="Write Python code that the action will execute. Some variables are "
|
||||
"available for use; help about python expression is given in the help tab.")
|
||||
|
||||
# ---------------------------------------- Relational -----------------------------------------
|
||||
user_ids = fields.Many2many('res.users', compute='_compute_user_ids', string="Approver Users")
|
||||
|
||||
approver_ids = fields.One2many('approval.category.approver', 'category_id', string="Approvers")
|
||||
conditions_ids = fields.One2many('approval.category.condition', 'category_id', string="Approval Condition")
|
||||
approver_sequence = fields.Boolean('Approvers Sequence?', help="If checked, the approvers have to approve in sequence (one after the other). If Employee's Manager is selected as approver, they will be the first in line.")
|
||||
|
||||
request_to_validate_count = fields.Integer("Number of requests to validate", compute="_compute_request_to_validate_count")
|
||||
@ -93,12 +94,28 @@ class ApprovalCategory(models.Model):
|
||||
sequence_code = fields.Char(string="Code")
|
||||
sequence_id = fields.Many2one('ir.sequence', 'Reference Sequence',
|
||||
copy=False, check_company=True)
|
||||
|
||||
model_id = fields.Many2one('ir.model', 'Model',copy=False)
|
||||
model_id = fields.Many2one('ir.model', string='Model', store=True)
|
||||
model_name = fields.Char(string='Model Name', related='model_id.model', store=True)
|
||||
|
||||
conditions_ids = fields.Many2many('approval.condition', 'approval_category_condition_rel', 'category_id', 'condition_id')
|
||||
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')
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------- 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
|
||||
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'])
|
||||
@ -106,29 +123,25 @@ class ApprovalCategory(models.Model):
|
||||
for category in self:
|
||||
category.request_to_validate_count = requests_mapped_data.get(category.id, 0)
|
||||
|
||||
|
||||
|
||||
|
||||
@api.depends_context('lang')
|
||||
@api.depends('approval_minimum', 'approver_ids', 'manager_approval')
|
||||
def _compute_invalid_minimum(self):
|
||||
for record in self:
|
||||
if record.approval_minimum > len(record.approver_ids) + int(bool(record.manager_approval)):
|
||||
record.invalid_minimum = True
|
||||
else:
|
||||
record.invalid_minimum = False
|
||||
record.invalid_minimum_warning = record.invalid_minimum and _('Your minimum approval exceeds the total of default approvers.')
|
||||
|
||||
# @api.depends_context('lang')
|
||||
# @api.depends('approval_minimum', 'approver_ids', 'manager_approval')
|
||||
# def _compute_invalid_minimum(self):
|
||||
# for record in self:
|
||||
# if record.approval_minimum > len(record.approver_ids) + int(bool(record.manager_approval)):
|
||||
# record.invalid_minimum = True
|
||||
# else:
|
||||
# record.invalid_minimum = False
|
||||
# record.invalid_minimum_warning = record.invalid_minimum and _('Your minimum approval exceeds the total of default approvers.')
|
||||
@api.depends('approver_ids')
|
||||
def _compute_user_ids(self):
|
||||
for record in self:
|
||||
record.user_ids = record.approver_ids.user_id
|
||||
|
||||
@api.constrains('approval_minimum', 'approver_ids')
|
||||
def _constrains_approval_minimum(self):
|
||||
for record in self:
|
||||
if record.approval_minimum < len(record.approver_ids.filtered('required')):
|
||||
raise ValidationError(_('Minimum Approval must be equal or superior to the sum of required Approvers.'))
|
||||
# @api.constrains('approval_minimum', 'approver_ids')
|
||||
# def _constrains_approval_minimum(self):
|
||||
# for record in self:
|
||||
# if record.approval_minimum < len(record.approver_ids.filtered('required')):
|
||||
# raise ValidationError(_('Minimum Approval must be equal or superior to the sum of required Approvers.'))
|
||||
|
||||
@api.constrains('approver_ids')
|
||||
def _constrains_approver_ids(self):
|
||||
@ -139,11 +152,10 @@ class ApprovalCategory(models.Model):
|
||||
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('approver_sequence', 'approval_minimum')
|
||||
def _constrains_approver_sequence(self):
|
||||
if any(a.approver_sequence and not a.approval_minimum for a in self):
|
||||
raise ValidationError(_('Approver Sequence can only be activated with at least 1 minimum approver.'))
|
||||
# @api.constrains('approver_sequence', 'approval_minimum')
|
||||
# def _constrains_approver_sequence(self):
|
||||
# if any(a.approver_sequence and not a.approval_minimum for a in self):
|
||||
# raise ValidationError(_('Approver Sequence can only be activated with at least 1 minimum approver.'))
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
|
@ -11,16 +11,28 @@ class ApprovalCategoryApprover(models.Model):
|
||||
_description = 'Approval Type Approver'
|
||||
_rec_name = 'user_id'
|
||||
_order = 'sequence'
|
||||
|
||||
sequence = fields.Integer('Sequence', default=10)
|
||||
category_id = fields.Many2one('approval.category', string='Approval Type', ondelete='cascade', required=True)
|
||||
category_id = fields.Many2one('approval.category', string='Approval Type', required=True)
|
||||
company_id = fields.Many2one('res.company', related='category_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)
|
||||
existing_user_ids = fields.Many2many('res.users', compute='_compute_existing_user_ids')
|
||||
required = fields.Boolean(default=False)
|
||||
|
||||
existing_user_ids = fields.Many2many('res.users', compute='_compute_existing_user_ids')
|
||||
|
||||
@api.depends('user_id', 'company_id')
|
||||
def _compute_job_title(self):
|
||||
for record in self:
|
||||
if record.user_id and record.company_id:
|
||||
# Search for the employee linked to the user and company
|
||||
employee = self.env['hr.employee'].search([
|
||||
('user_id', '=', record.user_id.id),
|
||||
('company_id', '=', record.company_id.id)
|
||||
], limit=1)
|
||||
record.job_title = employee.job_title if employee else ''
|
||||
else:
|
||||
record.job_title = ''
|
||||
@api.depends('category_id')
|
||||
def _compute_existing_user_ids(self):
|
||||
for record in self:
|
||||
|
72
extra-addons/approvals/models/approval_category_condition.py
Normal file
72
extra-addons/approvals/models/approval_category_condition.py
Normal file
@ -0,0 +1,72 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
class ApprovalConditions(models.Model):
|
||||
_name = 'approval.category.condition'
|
||||
_description = 'Approval Condition'
|
||||
_order = 'sequence, id'
|
||||
|
||||
sequence = fields.Integer(default=1)
|
||||
description = fields.Text(string="Description")
|
||||
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")
|
||||
model_id = fields.Many2one('ir.model', string='Model', related='category_id.model_id', store=True, readonly=True)
|
||||
model_name = fields.Char(
|
||||
string='Model Name',
|
||||
related='model_id.model',
|
||||
store=True,
|
||||
readonly=True
|
||||
)
|
||||
domain = fields.Char(string="Conditions", compute='_compute_domain', readonly=False, store=True)
|
||||
|
||||
# @api.depends('category_id.model_id')
|
||||
# def _compute_model_id(self):
|
||||
# """Compute the model_id from the related approval category."""
|
||||
# for record in self:
|
||||
# record.model_id = record.category_id.model_id
|
||||
|
||||
|
||||
@api.depends('model_id')
|
||||
def _compute_domain(self):
|
||||
for record in self:
|
||||
record.domain = '[]'
|
||||
|
||||
@api.depends('approval_minimum', 'approver_ids')
|
||||
def _compute_invalid_minimum(self):
|
||||
for record in self:
|
||||
if record.approval_minimum >= len(record.approver_ids):
|
||||
record.invalid_minimum = False
|
||||
else:
|
||||
record.invalid_minimum = True
|
||||
record.invalid_minimum_warning = record.invalid_minimum and _('Your minimum approval exceeds the total of default approvers.')
|
||||
|
||||
# @api.depends('approver_ids')
|
||||
# def _compute_user_ids(self):
|
||||
# for record in self:
|
||||
# record.user_ids = record.approver_ids.user_id
|
||||
|
||||
# @api.depends('model_name')
|
||||
# def _compute_filter_id(self):
|
||||
# for record in self:
|
||||
# record.filter_id = False
|
||||
|
||||
@api.constrains('approval_minimum', 'approver_ids')
|
||||
def _constrains_approval_minimum(self):
|
||||
for record in self:
|
||||
if record.approval_minimum < len(record.approver_ids):
|
||||
raise ValidationError(_('Minimum Approval must be equal or superior to the sum of required Approvers.'))
|
||||
|
||||
@api.constrains('approver_sequence', 'approval_minimum')
|
||||
def _constrains_approver_sequence(self):
|
||||
for record in self:
|
||||
if record.approver_sequence and record.approval_minimum < 1:
|
||||
raise ValidationError(_('Approver Sequence can only be activated with at least 1 minimum approver.'))
|
||||
|
||||
def delete_condition(self):
|
||||
for record in self:
|
||||
record.unlink()
|
||||
|
@ -1,77 +0,0 @@
|
||||
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()
|
||||
|
33
extra-addons/approvals/models/approval_promotion_line.py
Normal file
33
extra-addons/approvals/models/approval_promotion_line.py
Normal file
@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
class ApprovalPromotionLine(models.Model):
|
||||
_name = 'approval.promotion.line'
|
||||
_description = 'Promote Line'
|
||||
|
||||
_check_company_auto = True
|
||||
|
||||
approval_request_id = fields.Many2one('approval.request', required=True)
|
||||
description = fields.Char(
|
||||
"Description", required=True,
|
||||
compute="_compute_promotion", store=True, readonly=False, precompute=True)
|
||||
|
||||
company_id = fields.Many2one(
|
||||
string='Company', related='approval_request_id.company_id',
|
||||
store=True, readonly=True, index=True)
|
||||
promote_id = fields.Many2one('hr.promote', string="Promotion", check_company=True)
|
||||
current_job = fields.Char(
|
||||
"Current Job", required=True,
|
||||
compute="_compute_promotion", store=True, readonly=True, precompute=True)
|
||||
designation_job = fields.Char(
|
||||
"New Designation", required=True,
|
||||
compute="_compute_promotion", store=True, readonly=True, precompute=True)
|
||||
@api.depends('promote_id')
|
||||
def _compute_promotion(self):
|
||||
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 ''
|
||||
|
@ -14,7 +14,7 @@ class ApprovalRequest(models.Model):
|
||||
_check_company_auto = True
|
||||
|
||||
name = fields.Char(string="Approval Subject", tracking=True)
|
||||
category_id = fields.Many2one('approval.category', string="Category", required=True)
|
||||
category_id = fields.Many2one('approval.category', string="Category", required=True,ondelete='cascade')
|
||||
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)
|
||||
@ -57,7 +57,7 @@ class ApprovalRequest(models.Model):
|
||||
attachment_ids = fields.One2many(comodel_name='ir.attachment', inverse_name='res_id', domain=[('res_model', '=', 'approval.request')], string='Attachments')
|
||||
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)
|
||||
|
||||
promotion_line_ids = fields.One2many('approval.promotion.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")
|
||||
@ -67,12 +67,13 @@ class ApprovalRequest(models.Model):
|
||||
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")
|
||||
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")
|
||||
approver_sequence = fields.Boolean(related="category_id.approver_sequence")
|
||||
automated_sequence = fields.Boolean(related="category_id.automated_sequence")
|
||||
|
||||
@api.depends('approver_ids')
|
||||
def _compute_user_ids(self):
|
||||
for request in self:
|
||||
@ -105,11 +106,23 @@ class ApprovalRequest(models.Model):
|
||||
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)
|
||||
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:
|
||||
|
||||
return
|
||||
return
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def unlink_attachments(self):
|
||||
attachment_ids = self.env['ir.attachment'].search([
|
||||
@ -121,6 +134,7 @@ class ApprovalRequest(models.Model):
|
||||
|
||||
def unlink(self):
|
||||
self.filtered(lambda a: a.has_product).product_line_ids.unlink()
|
||||
self.filtered(lambda a: a.has_promotion).promotion_line_ids.unlink()
|
||||
return super().unlink()
|
||||
|
||||
def action_get_attachment_view(self):
|
||||
|
@ -6,6 +6,7 @@ access_approval_category,access_approval_category,model_approval_category,base.g
|
||||
access_approval_category_user,access_approval_category,model_approval_category,group_approval_user,1,0,0,0
|
||||
access_approval_category_manager,access_approval_category,model_approval_category,group_approval_manager,1,1,1,1
|
||||
access_approval_category_approver,access_approval_category_approver,model_approval_category_approver,base.group_user,1,0,0,0
|
||||
access_approval_category_condition,access_approval_category_condition,model_approval_category_condition,base.group_user,1,1,1,1
|
||||
access_approval_category_approver_user,access_approval_category_approver,model_approval_category_approver,group_approval_user,1,0,0,0
|
||||
access_approval_category_approver_manager,access_approval_category_approver,model_approval_category_approver,group_approval_manager,1,1,1,1
|
||||
access_approval_approver,access_approval_approver,model_approval_approver,base.group_user,1,1,1,1
|
||||
@ -13,5 +14,8 @@ 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_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_condition,access_approval_condition,model_approval_condition,base.group_user,1,1,1,1
|
||||
access_approval_promotion_line_user,access_approval_promotion_line,model_approval_promotion_line,base.group_user,1,1,1,1
|
||||
access_approval_promotion_line,access_approval_promotion_line,model_approval_promotion_line,base.group_user,1,1,1,1
|
||||
access_approval_promotion_line_user,access_approval_promotion_line,model_approval_promotion_line,base.group_user,1,1,1,1
|
||||
access_approval_promotion_line_manager,access_approval_promotion_line,model_approval_promotion_line,group_approval_manager,1,1,1,1
|
||||
|
||||
|
|
@ -1,52 +1,52 @@
|
||||
.o_approvals_category_actions_field,
|
||||
.o_approvals_category_kanban_view {
|
||||
.o_kanban_ungrouped {
|
||||
padding: 0;
|
||||
// .o_approvals_category_actions_field,
|
||||
// .o_approvals_category_kanban_view {
|
||||
// .o_kanban_ungrouped {
|
||||
// padding: 0;
|
||||
|
||||
.o_kanban_record {
|
||||
width: 30%;
|
||||
margin: 0;
|
||||
// .o_kanban_record {
|
||||
// width: 30%;
|
||||
// margin: 0;
|
||||
|
||||
&.o_kanban_ghost {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// &.o_kanban_ghost {
|
||||
// display: none;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
.o_approvals_category_actions_field .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;
|
||||
// .o_approvals_category_kanban_view {
|
||||
// .o_kanban_grouped .row {
|
||||
// flex-direction: column !important;
|
||||
// gap: 0.5rem !important;
|
||||
|
||||
> * {
|
||||
width: 100% !important;
|
||||
// > * {
|
||||
// width: 100% !important;
|
||||
|
||||
> * {
|
||||
margin: 0 0.5rem !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_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;
|
||||
}
|
||||
}
|
||||
// .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;
|
||||
// }
|
||||
// }
|
||||
|
@ -10,6 +10,7 @@
|
||||
<field name="company_id" column_invisible="True"/>
|
||||
<field name="sequence" widget="handle" />
|
||||
<field name="user_id" options="{'no_create': True}"/>
|
||||
<field name="job_title"/>
|
||||
<field name="required"/>
|
||||
</list>
|
||||
</field>
|
||||
|
@ -70,60 +70,47 @@
|
||||
<group>
|
||||
<field name="description"/>
|
||||
<field name="model_id"/>
|
||||
<field name="model_name" invisible="1"/>
|
||||
<field name="approval_type" invisible="1"/>
|
||||
<field name="automated_sequence"/>
|
||||
<field name="sequence_code" invisible="not automated_sequence" required="automated_sequence"/>
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Options">
|
||||
<!-- <group string="Fields" name="option_settings">
|
||||
<field name="active" invisible="1"/>
|
||||
<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_date" string="Date" 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_quantity" string="Quantity" widget="radio" options="{'horizontal': true}"/>
|
||||
<field name="has_amount" string="Amount" widget="radio" options="{'horizontal': true}"/>
|
||||
<field name="has_reference" string="Reference" widget="radio" options="{'horizontal': true}"/>
|
||||
<field name="has_payment_method" string="Payment" widget="radio" options="{'horizontal': true}" invisible="1"/>
|
||||
<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> -->
|
||||
<page string="Conditions">
|
||||
<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>
|
||||
<field name="conditions_ids" nolabel="1"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Options">
|
||||
<group string="Fields" name="option_settings">
|
||||
<field name="active" invisible="1"/>
|
||||
<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_date" string="Date" 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_promotion" string="Promotion" force_save="1" widget="radio" options="{'horizontal': true}"/>
|
||||
<field name="has_quantity" string="Quantity" widget="radio" options="{'horizontal': true}"/>
|
||||
<field name="has_amount" string="Amount" widget="radio" options="{'horizontal': true}"/>
|
||||
<field name="has_reference" string="Reference" widget="radio" options="{'horizontal': true}"/>
|
||||
<field name="has_payment_method" string="Payment" widget="radio" options="{'horizontal': true}" invisible="1"/>
|
||||
<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> -->
|
||||
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
|
@ -1,26 +1,26 @@
|
||||
<?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="name">approval.category.condition.form</field>
|
||||
<field name="model">approval.category.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']" /> -->
|
||||
<field name="model_id"/>
|
||||
<field name="model_name" invisible="1"/>
|
||||
<field name="domain" widget="domain" options="{'model': 'model_name'}"/>
|
||||
</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="approver_ids" domain="domain"/>
|
||||
<field name="sequence"/>
|
||||
<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>
|
||||
<field name="description"/>
|
||||
</group>
|
||||
</sheet>
|
||||
@ -29,16 +29,22 @@
|
||||
</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="name">approval.category.condition.list</field>
|
||||
<field name="model">approval.category.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" />
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="description" colspan="2"/>
|
||||
<field name="domain" colspan="3"/>
|
||||
<!-- <field name="approver_sequence" colspan="3"/>
|
||||
<field name="approval_minimum" colspan="3"/> -->
|
||||
<!-- <button name="delete_condition" type="object" class="btn fa fa-trash fa-xl px-3 ms-auto" title="Delete Condition" /> -->
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
<record id="approval_condition_action" model="ir.actions.act_window">
|
||||
<field name="name">Approvals Conditions</field>
|
||||
<field name="res_model">approval.category.condition</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record id="approval_promotion_line_view_tree" model="ir.ui.view">
|
||||
<field name="name">approval.promotion.line.list</field>
|
||||
<field name="model">approval.promotion.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<list editable="bottom" string="Promotions">
|
||||
<field name="company_id" column_invisible="True"/>
|
||||
<field name="promote_id"/>
|
||||
<field name="current_job" />
|
||||
<field name="designation_job"/>
|
||||
<field name="description"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="approval_promotion_line_view_form" model="ir.ui.view">
|
||||
<field name="name">approval.promotion.line.form</field>
|
||||
<field name="model">approval.promotion.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Promotions">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<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}"/> -->
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="approval_promotion_kanban_mobile_view" model="ir.ui.view">
|
||||
<field name="name">approval.promotion.kanban.mobile</field>
|
||||
<field name="model">approval.promotion.line</field>
|
||||
<field name="priority">15</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban editable="bottom" string="Promotion">
|
||||
<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"/>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
14
extra-addons/approvals/views/approval_promotion_views.xml
Normal file
14
extra-addons/approvals/views/approval_promotion_views.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record id="promotion_action" model="ir.actions.act_window">
|
||||
<field name="name">Promotions</field>
|
||||
<field name="res_model">hr.promote</field>
|
||||
<field name="search_view_id" ref="action_hr_promote"/>
|
||||
<field name="view_mode">kanban,list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No promotion found. Let's create one!
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
@ -122,6 +122,7 @@
|
||||
<field name="has_payment_method" invisible="1"/>
|
||||
<field name="has_location" invisible="1"/>
|
||||
<field name="has_product" invisible="1"/>
|
||||
<field name="has_promotion" invisible="1"/>
|
||||
<field name="requirer_document" invisible="1"/>
|
||||
<field name="approval_minimum" invisible="1"/>
|
||||
<field name="approval_type" invisible="1"/>
|
||||
@ -187,6 +188,12 @@
|
||||
context="{'list_view_ref': 'approvals.approval_product_line_view_tree', 'kanban_view_ref': 'approvals.approval_product_kanban_mobile_view'}"
|
||||
readonly="request_status != 'new'"/>
|
||||
</page>
|
||||
<page string="Promotions" name="promotions"
|
||||
invisible="has_promotion == 'no'">
|
||||
<field name="promotion_line_ids"
|
||||
context="{'list_view_ref': 'approvals.approval_promotion_line_view_tree', 'kanban_view_ref': 'approvals.approval_promotion_kanban_mobile_view'}"
|
||||
readonly="request_status != 'new'"/>
|
||||
</page>
|
||||
<page string="Description" name="description">
|
||||
<field name="reason" readonly="request_status != 'new'"/>
|
||||
</page>
|
||||
@ -357,4 +364,15 @@
|
||||
action="product_product_action"
|
||||
groups="product.group_product_variant"
|
||||
sequence="20"/>
|
||||
<!-- <menuitem
|
||||
id="approvals_menu_employee"
|
||||
parent="approvals_menu_config"
|
||||
name="Employees"
|
||||
sequence="40"/>
|
||||
<menuitem
|
||||
id="approvals_menu_promotion_template"
|
||||
parent="approvals_menu_employee"
|
||||
name="promotion"
|
||||
action="promotion_action"
|
||||
sequence="10"/> -->
|
||||
</odoo>
|
||||
|
@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": 'Employee Promote',
|
||||
"category": 'Hidden',
|
||||
"name": 'Employee Promotion',
|
||||
"category": 'Human Resources/Employees',
|
||||
"version": '0.1',
|
||||
"description":
|
||||
"""
|
||||
Promote Module for HR
|
||||
""",
|
||||
"depends": ['hr','approvals'],
|
||||
'auto_install': ['hr','approvals'],
|
||||
"depends": ['base','hr'],
|
||||
'auto_install': True,
|
||||
"data": [
|
||||
'security/ir.model.access.csv',
|
||||
'views/hr_promote_views.xml',
|
||||
'views/hr_promote_menus.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
"license": "LGPL-3",
|
||||
}
|
@ -6,7 +6,6 @@ class HRPromote(models.Model):
|
||||
_name = 'hr.promote'
|
||||
_description = 'Employees/Promotes'
|
||||
_order = 'promotion_date desc'
|
||||
|
||||
# ----------------------------------- Fields Declaration ----------------------------------
|
||||
promotion_date = fields.Date(
|
||||
string="Promotion Date",
|
||||
@ -33,6 +32,17 @@ class HRPromote(models.Model):
|
||||
)
|
||||
|
||||
# ---------------------------------------- Relational -----------------------------------------
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string="Company",
|
||||
copy=False,
|
||||
readonly=True,
|
||||
required=True,
|
||||
index=True,
|
||||
default=lambda self: self.env.company,
|
||||
help="The company this promotion belongs to."
|
||||
)
|
||||
|
||||
employee_id = fields.Many2one(
|
||||
'hr.employee',
|
||||
string="Employee",
|
||||
@ -58,7 +68,7 @@ class HRPromote(models.Model):
|
||||
)
|
||||
|
||||
# ---------------------------------------- Compute Value -----------------------------------------
|
||||
name = fields.Char(string="Promotion Reference", compute="_compute_name", store=True)
|
||||
display_name = fields.Char(string="Promotion Reference", compute="_compute_name", store=True)
|
||||
|
||||
# ---------------------------------------- Methods -----------------------------------------
|
||||
|
||||
@ -67,33 +77,34 @@ class HRPromote(models.Model):
|
||||
def _compute_name(self):
|
||||
for record in self:
|
||||
if record.employee_id and record.promotion_date:
|
||||
record.name = f"{record.employee_id.name} - {record.promotion_date}"
|
||||
record.display_name = f"{record.employee_id.name} - {record.promotion_date}"
|
||||
else:
|
||||
record.name = "Promotion"
|
||||
@api.model
|
||||
record.display_name = "Promotion"
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
# Ensure vals_list is always a list
|
||||
if isinstance(vals_list, dict):
|
||||
vals_list = [vals_list]
|
||||
|
||||
promotion_records = []
|
||||
approval_requests_to_create = []
|
||||
for vals in vals_list:
|
||||
# Set default state
|
||||
# Ensure the record is linked to the current company
|
||||
if 'company_id' not in vals:
|
||||
vals['company_id'] = self.env.company.id
|
||||
|
||||
vals['state'] = 'approval_needed'
|
||||
|
||||
# Handle employee's job_id logic
|
||||
if 'employee_id' in vals and not vals.get('job_id'):
|
||||
employee = self.env['hr.employee'].browse(vals['employee_id'])
|
||||
vals['job_id'] = employee.job_id.id if employee.job_id else False
|
||||
|
||||
# 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)
|
||||
|
||||
# Create an Approval request
|
||||
|
||||
return self.browse([record.id for record in promotion_records])
|
||||
def action_save_and_close(self):
|
||||
return {
|
||||
|
@ -1,2 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_hr_promote,hr.promote,model_hr_promote,hr.group_hr_user,1,1,1,1
|
||||
access_hr_promote,hr.promote,model_hr_promote,base.group_user,1,1,1,1
|
|
@ -6,10 +6,12 @@
|
||||
<field name="model">hr.promote</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Promote">
|
||||
<field name="company_id" width="20%" column_invisible="True"/>
|
||||
<field name="employee_id" width="20%"/>
|
||||
<field name="description" width="30%"/>
|
||||
<field name="job_id" readonly="1" width="15%"/>
|
||||
<field name="designation_id" width="15%"/>
|
||||
<field name="state" width="15%"/>
|
||||
<field name="promotion_date" width="20%"/>
|
||||
</list>
|
||||
</field>
|
||||
@ -49,5 +51,6 @@
|
||||
<field name="name">Promote</field>
|
||||
<field name="res_model">hr.promote</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="domain">[('company_id', 'in', allowed_company_ids)]</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
Loading…
Reference in New Issue
Block a user