first push message

This commit is contained in:
2026-07-01 14:41:49 +07:00
parent 6667dec2bf
commit 58b5f46cc4
2951 changed files with 316619 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
from . import controllers
from . import models
+40
View File
@@ -0,0 +1,40 @@
{
'name': "xf_doc_approval",
'summary': "Short (1 phrase/line) summary of the module's purpose",
'description': """
Long description of module's purpose
""",
'author': "My Company",
'license': 'LGPL-3',
'website': "https://www.yourcompany.com",
# Categories can be used to filter modules in modules listing
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
# for the full list
'category': 'request approval',
'version': '0.1',
# any module necessary for this one to work correctly
'depends': ['base', 'web', 'mail','account'],
'data': [
# always loaded
# Access
'security/security.xml',
'security/ir.model.access.csv',
# Views
'views/menuitems.xml',
'views/team.xml',
'views/document_package.xml',
'views/approver_wizard.xml',
# Data
'data/mail_templates.xml',
'data/mail_message_subtypes.xml',
],
'installable': True,
'auto_install': False,
'application': True,
}
+1
View File
@@ -0,0 +1 @@
from . import controllers
@@ -0,0 +1,21 @@
# from odoo import http
# class XfDocApproval(http.Controller):
# @http.route('/xf_doc_approval/xf_doc_approval', auth='public')
# def index(self, **kw):
# return "Hello, world"
# @http.route('/xf_doc_approval/xf_doc_approval/objects', auth='public')
# def list(self, **kw):
# return http.request.render('xf_doc_approval.listing', {
# 'root': '/xf_doc_approval/xf_doc_approval',
# 'objects': http.request.env['xf_doc_approval.xf_doc_approval'].search([]),
# })
# @http.route('/xf_doc_approval/xf_doc_approval/objects/<model("xf_doc_approval.xf_doc_approval"):obj>', auth='public')
# def object(self, obj, **kw):
# return http.request.render('xf_doc_approval.object', {
# 'object': obj
# })
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="mt_document_package_approval" model="mail.message.subtype">
<field name="name">Document Package Sent for Approval</field>
<field name="res_model">xf.doc.approval.document.package</field>
<field name="default" eval="False"/>
<field name="description">Document Package sent for approval</field>
</record>
<record id="mt_document_package_approved" model="mail.message.subtype">
<field name="name">Document Package Approved</field>
<field name="res_model">xf.doc.approval.document.package</field>
<field name="default" eval="True"/>
<field name="description">Document Package was approved by all approvers</field>
</record>
<record id="mt_document_package_rejected" model="mail.message.subtype">
<field name="name">Document Package Rejected</field>
<field name="res_model">xf.doc.approval.document.package</field>
<field name="default" eval="True"/>
<field name="description">Document Package was rejected. Please see notes in the approvers tab.</field>
</record>
<record id="mt_document_package_cancelled" model="mail.message.subtype">
<field name="name">Document Package Cancelled</field>
<field name="res_model">xf.doc.approval.document.package</field>
<field name="default" eval="True"/>
<field name="description">Document Package was cancelled.</field>
</record>
</data>
</odoo>
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<template id="request_to_approve">
<p>
Dear colleagues,
</p>
<p>
Please be informed that the document package
"<t t-esc="object.name"/>"
needs approval from:
<t t-esc="', '.join(object.get_current_approvers().mapped('display_name'))"/>.
</p>
<p>
<a t-att-href="'/mail/view?model=%s&amp;res_id=%s' % (object._name, object.id)">
View
<t t-esc="object._description"/>
</a>
</p>
</template>
</data>
</odoo>
+30
View File
@@ -0,0 +1,30 @@
<odoo>
<data>
<!--
<record id="object0" model="xf_doc_approval.xf_doc_approval">
<field name="name">Object 0</field>
<field name="value">0</field>
</record>
<record id="object1" model="xf_doc_approval.xf_doc_approval">
<field name="name">Object 1</field>
<field name="value">10</field>
</record>
<record id="object2" model="xf_doc_approval.xf_doc_approval">
<field name="name">Object 2</field>
<field name="value">20</field>
</record>
<record id="object3" model="xf_doc_approval.xf_doc_approval">
<field name="name">Object 3</field>
<field name="value">30</field>
</record>
<record id="object4" model="xf_doc_approval.xf_doc_approval">
<field name="name">Object 4</field>
<field name="value">40</field>
</record>
-->
</data>
</odoo>
+2
View File
@@ -0,0 +1,2 @@
from . import document
from . import team
+313
View File
@@ -0,0 +1,313 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError, AccessError
from .selection import ApprovalMethods, DocumentState, ApproverState, ApprovalStep, DocumentVisibility
class DocApprovalDocumentPackage(models.Model):
_name = 'xf.doc.approval.document.package'
_inherit = ['mail.thread']
_description = 'Document Package'
active = fields.Boolean(default=True)
name = fields.Char(
string='Name',
required=True,
translate=True,
readonly=True,
tracking=True,
)
description = fields.Text(
string='Description',
translate=True,
)
state = fields.Selection(
string='Status',
selection=DocumentState.list,
required=True,
default=DocumentState.default,
readonly=True,
tracking=True,
)
approval_state = fields.Selection(
string='Approval Status',
selection=ApproverState.list,
compute='_compute_approval_state',
)
approval_step = fields.Selection(
string='Approval Step',
selection=ApprovalStep.list,
compute='_compute_approval_step',
)
method = fields.Selection(
string='Approval Method',
selection=ApprovalMethods.list,
required=True,
default=ApprovalMethods.default,
readonly=True,
)
visibility = fields.Selection(
string='Visibility',
selection=DocumentVisibility.list,
required=True,
default=DocumentVisibility.default,
)
initiator_user_id = fields.Many2one(
string='Initiator',
comodel_name='res.users',
required=True,
default=lambda self: self.env.user,
readonly=True,
)
company_id = fields.Many2one(
string='Company',
comodel_name='res.company',
required=True,
default=lambda self: self.env.company,
readonly=True,
)
approval_team_id = fields.Many2one(
string='Approval Team',
comodel_name='xf.doc.approval.team',
readonly=True,
domain="[('company_id', '=', company_id)]",
)
approver_ids = fields.One2many(
string='Approvers',
comodel_name='xf.doc.approval.document.approver',
inverse_name='document_package_id',
readonly=True,
)
document_ids = fields.One2many(
string='Documents',
comodel_name='xf.doc.approval.document',
inverse_name='document_package_id',
readonly=True,
)
is_initiator = fields.Boolean('Is Initiator', compute='_compute_access')
is_approver = fields.Boolean('Is Approver', compute='_compute_access')
reject_reason = fields.Text('Reject Reason')
# Compute fields
@api.depends('state', 'approval_team_id')
def _compute_access(self):
for record in self:
# Check if the current user is initiator (true for admin)
record.is_initiator = self.env.user == record.initiator_user_id or self.env.user._is_admin()
# Check if the document needs approval from current user (true for admin)
current_approvers = record.get_current_approvers()
responsible = self.env.user in current_approvers.mapped('user_id') or self.env.user._is_admin()
record.is_approver = record.approval_state == 'pending' and responsible
@api.depends('approver_ids.state')
def _compute_approval_state(self):
for record in self:
approvers = record.approver_ids
if len(approvers) == len(approvers.filtered(lambda a: a.state == 'approved')):
record.approval_state = 'approved'
elif approvers.filtered(lambda a: a.state == 'rejected'):
record.approval_state = 'rejected'
elif approvers.filtered(lambda a: a.state == 'pending'):
record.approval_state = 'pending'
else:
record.approval_state = 'to approve'
@api.depends('approver_ids.state', 'approver_ids.step')
def _compute_approval_step(self):
for record in self:
approval_step = None
steps = record.approver_ids.mapped('step')
steps.sort()
for step in steps:
if record.approver_ids.filtered(lambda a: a.step == step and a.state != 'approved'):
approval_step = step
break
record.approval_step = approval_step
# Onchange handlers
@api.onchange('approval_team_id')
def onchange_approval_team(self):
if self.approval_team_id:
team_approvers = []
for team_approver in self.approval_team_id.approver_ids:
team_approvers += [{
'step': team_approver.step,
'user_id': team_approver.user_id.id,
'role': team_approver.role,
}]
approvers = self.approver_ids.browse([])
for a in team_approvers:
approvers += approvers.new(a)
self.approver_ids = approvers
@api.onchange('approver_ids')
def onchange_approvers(self):
if self.approval_team_id:
if self.approval_team_id.approver_ids.mapped('user_id') != self.approver_ids.mapped('user_id'):
self.approval_team_id = None
# Validation
@api.constrains('company_id')
def _validate_company(self):
for record in self:
record.approver_ids.validate_company(record.company_id)
@api.constrains('state', 'approver_ids')
def _check_approvers(self):
for record in self:
if record.state == 'approval' and not record.approver_ids:
raise ValidationError(_('Please add at least one approver!'))
@api.constrains('state', 'document_ids')
def _check_documents(self):
for record in self:
if record.state == 'approval' and not record.document_ids:
raise ValidationError(_('Please add at least one document!'))
# Helpers
def set_state(self, state, vals=None):
if vals is None:
vals = {}
vals.update({'state': state})
return self.write(vals)
def get_next_approvers(self):
self.ensure_one()
next_approvers = self.approver_ids.filtered(lambda a: a.state == 'to approve').sorted('step')
if not next_approvers:
return next_approvers
next_step = next_approvers[0].step
return next_approvers.filtered(lambda a: a.step == next_step)
def get_current_approvers(self):
self.ensure_one()
return self.approver_ids.filtered(lambda a: a.state == 'pending' and a.step == self.approval_step)
def get_current_approver(self):
self.ensure_one()
current_approvers = self.get_current_approvers()
if not current_approvers:
raise UserError(_('There are not approvers for this document package!'))
current_approver = current_approvers.filtered(lambda a: a.user_id == self.env.user)
if not current_approver and self.env.user._is_admin():
current_approver = current_approvers[0]
if not current_approver:
raise AccessError(_('You are not allowed to approve this document package!'))
return current_approver
def send_notification(self, view_ref, partner_ids):
for record in self:
record.message_post_with_source(
view_ref,
subject=_('Document Approval: %s') % record.name,
partner_ids=partner_ids,
subtype_xmlid='mail.mt_note',
)
# User actions
def action_send_for_approval(self):
for record in self:
if record.state == 'draft' and record.approver_ids:
# Subscribe approvers
record.message_subscribe(partner_ids=record.approver_ids.mapped('user_id').mapped('partner_id').ids)
if record.approval_state == 'pending':
raise UserError(_('The document package have already been sent for approval!'))
elif record.approval_state == 'approved':
raise UserError(_('The document package have already been approved!'))
elif record.approval_state == 'rejected':
raise UserError(_('The document package was rejected! To send it for approval again, please update document(s) first.'))
elif record.approval_state == 'to approve':
next_approvers = record.get_next_approvers()
if next_approvers:
if record.state == 'draft':
record.state = 'approval'
next_approvers.write({'state': 'pending'})
partner_ids = next_approvers.mapped('user_id').mapped('partner_id').ids
record.send_notification('xf_doc_approval.request_to_approve', partner_ids)
else:
raise UserError(_('There are not approvers for this document package!'))
def action_approve_wizard(self):
self.ensure_one()
current_approver = self.get_current_approver()
return current_approver.action_wizard('action_approve_wizard', _('Approve'))
def action_reject_wizard(self):
self.ensure_one()
current_approver = self.get_current_approver()
return current_approver.action_wizard('action_reject_wizard', _('Reject'))
def action_draft(self):
for record in self:
record.approver_ids.write({'state': 'to approve', 'notes': None})
record.write({'state': 'draft', 'reject_reason': None})
return True
def action_cancel(self):
if not self.env.user._is_admin() and self.filtered(lambda record: record.state == 'approved'):
raise UserError(_("Cannot cancel a document package that is approved."))
return self.set_state('cancelled')
def action_finish_approval(self):
for record in self:
if record.approval_state == 'approved':
record.state = 'approved'
else:
raise UserError(_('Document Package must be fully approved!'))
# Built-in methods
def unlink(self):
if any(self.filtered(lambda record: record.state not in ('draft', 'cancelled'))):
raise UserError(_('You cannot delete a record which is not draft or cancelled!'))
return super(DocApprovalDocumentPackage, self).unlink()
def _track_subtype(self, init_values):
self.ensure_one()
if 'state' in init_values and self.state == 'approval':
return self.env.ref('xf_doc_approval.mt_document_package_approval')
elif 'state' in init_values and self.state == 'approved':
return self.env.ref('xf_doc_approval.mt_document_package_approved')
elif 'state' in init_values and self.state == 'cancelled':
return self.env.ref('xf_doc_approval.mt_document_package_cancelled')
elif 'state' in init_values and self.state == 'rejected':
return self.env.ref('xf_doc_approval.mt_document_package_rejected')
return super(DocApprovalDocumentPackage, self)._track_subtype(init_values)
class DocApprovalDocument(models.Model):
_name = 'xf.doc.approval.document'
_description = 'Document'
document_package_id = fields.Many2one(
string='Document Package',
comodel_name='xf.doc.approval.document.package',
required=True,
ondelete='cascade',
)
name = fields.Char(
string='Name',
required=True,
translate=True,
)
file = fields.Binary(
string='File',
required=True,
attachment=True,
)
file_name = fields.Char(
string='File Name'
)
@api.onchange('file_name')
def _onchange_file_name(self):
if self.file_name and not self.name:
self.name = self.file_name
+57
View File
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
class Selection(object):
list = []
folded = []
default = None
@classmethod
def name(cls, state):
states_dict = dict(cls.list)
if state in states_dict:
return states_dict[state]
@classmethod
def values(cls):
return list(dict(cls.list))
class ApproverState(Selection):
list = [
('to approve', 'To Approve'),
('pending', 'Pending'),
('approved', 'Approved'),
('rejected', 'Rejected'),
]
default = list[0][0]
class ApprovalMethods(Selection):
list = [
('button', 'Button'),
]
default = list[0][0]
class DocumentState(Selection):
list = [
('draft', 'Draft'),
('approval', 'Approval'),
('approved', 'Approved'),
('cancelled', 'Cancelled'),
('rejected', 'Rejected'),
]
default = list[0][0]
class DocumentVisibility(Selection):
list = [
('all_users', 'All Users'),
('followers', 'Followers'),
('approvers', 'Approvers'),
]
default = list[0][0]
class ApprovalStep(Selection):
step_range = list(range(1, 21))
list = [("{:02d}".format(step), "{:02d}".format(step)) for step in step_range]
default = list[0][0]
+195
View File
@@ -0,0 +1,195 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from .selection import ApproverState, ApprovalMethods, ApprovalStep
class DocApprovalTeam(models.Model):
_name = 'xf.doc.approval.team'
_description = 'Doc Approval Team'
active = fields.Boolean('Active', default=True)
name = fields.Char(
string='Name',
required=True,
)
user_id = fields.Many2one(
string='Team Leader',
comodel_name='res.users',
required=True,
default=lambda self: self.env.user,
index=True
)
company_id = fields.Many2one(
string='Company',
comodel_name='res.company',
required=True,
default=lambda self: self.env.company.id,
index=True,
)
approver_ids = fields.One2many(
string='Approvers',
comodel_name='xf.doc.approval.team.approver',
inverse_name='team_id',
)
# Validation
@api.constrains('company_id')
def _check_team_company(self):
for team in self:
team.approver_ids.validate_company(team.company_id)
class DocApprovalApproverAbstract(models.Model):
_name = 'xf.doc.approval.approver.abstract'
_description = 'Abstract Approver'
_order = 'step'
_rec_name = 'user_id'
step = fields.Selection(
string='Step',
selection=ApprovalStep.list,
required=True,
default=ApprovalStep.default,
)
user_id = fields.Many2one(
string='Approver',
comodel_name='res.users',
required=True
)
role = fields.Char(
string='Role/Position',
required=True,
default="Approver"
)
# Onchange handlers
@api.onchange('user_id')
def _detect_user_role(self):
for approver in self:
# if user related to employee, try to get job title for hr.employee
employee = hasattr(approver.user_id, 'employee_ids') and getattr(approver.user_id, 'employee_ids')
employee_job_id = hasattr(employee, 'job_id') and getattr(employee, 'job_id')
employee_job_title = employee_job_id.name if employee_job_id else False
if employee_job_title:
approver.role = employee_job_title
continue
# if user related partner, try to get job title for res.partner
partner = approver.user_id.partner_id
partner_job_title = hasattr(partner, 'function') and getattr(partner, 'function')
if partner_job_title:
approver.role = partner_job_title
# Validation
@api.constrains('user_id')
def _check_users(self):
for approver in self:
if not approver.user_id.has_group('xf_doc_approval.group_xf_doc_approval_user'):
raise ValidationError(_('%s does not have access to the Doc Approval module.') % (approver.user_id.name,)
+ '\n' +
_('Please ask system administrator to add him/her to the Doc Approval module group first.'))
def validate_company(self, company):
if not company:
return
for approver in self:
if company not in approver.user_id.company_ids:
raise ValidationError(
_('%s does not have access to the company %s') % (approver.user_id.name, company.name))
class DocApprovalTeamApprover(models.Model):
_name = 'xf.doc.approval.team.approver'
_inherit = ['xf.doc.approval.approver.abstract']
_description = 'Approval Team Member'
team_id = fields.Many2one(
string='Team',
comodel_name='xf.doc.approval.team',
required=True,
ondelete='cascade'
)
# Validation
@api.constrains('user_id', 'team_id')
def _check_users(self):
for approver in self:
approver.validate_company(approver.team_id.company_id)
return super(DocApprovalTeamApprover, self)._check_users()
class DocApprovalDocumentApprover(models.Model):
_name = 'xf.doc.approval.document.approver'
_inherit = ['xf.doc.approval.approver.abstract']
_description = 'Doc Approver'
team_approver_id = fields.Many2one(
string='Doc Team Approver',
comodel_name='xf.doc.approval.team.approver',
ondelete='set null'
)
document_package_id = fields.Many2one(
string='Document Package',
comodel_name='xf.doc.approval.document.package',
required=True,
ondelete='cascade',
)
method = fields.Selection(
string='Method',
related='document_package_id.method',
readonly=True,
)
state = fields.Selection(
string='Status',
selection=ApproverState.list,
readonly=True,
required=True,
default=ApproverState.default
)
notes = fields.Text(
string='Notes',
readonly=True,
)
# Validation
@api.constrains('user_id', 'document_package_id')
def _check_users(self):
for approver in self:
approver.validate_company(approver.document_package_id.company_id)
return super(DocApprovalDocumentApprover, self)._check_users()
# User actions
def action_wizard(self, view_ref, window_title):
self.ensure_one()
view = self.env.ref('xf_doc_approval.' + view_ref)
return {
'name': window_title,
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': self._name,
'res_id': self.id,
'views': [(view.id, 'form')],
'view_id': view.id,
'target': 'new'
}
def action_approve(self):
for approver in self:
document_package = approver.document_package_id
approver.state = 'approved'
if document_package.approval_state == 'to approve':
document_package.sudo().action_send_for_approval()
elif document_package.approval_state == 'approved':
document_package.sudo().action_finish_approval()
def action_reject(self):
for approver in self:
approver.state = 'rejected'
approver.document_package_id.sudo().set_state('rejected', {'reject_reason': approver.notes})
@@ -0,0 +1,15 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_user_xf_doc_approval_team,access_user_xf_doc_approval_team,model_xf_doc_approval_team,xf_doc_approval.group_xf_doc_approval_user,1,0,0,0
access_user_xf_doc_approval_team_approver,access_user_xf_doc_approval_team_approver,model_xf_doc_approval_team_approver,xf_doc_approval.group_xf_doc_approval_user,1,0,0,0
access_user_xf_doc_approval_document_approver,access_user_xf_doc_approval_document_approver,model_xf_doc_approval_document_approver,xf_doc_approval.group_xf_doc_approval_user,1,1,0,0
access_user_xf_doc_approval_document,access_user_xf_doc_approval_document,model_xf_doc_approval_document,xf_doc_approval.group_xf_doc_approval_user,1,0,0,0
access_user_xf_doc_approval_document_package,access_user_xf_doc_approval_document_package,model_xf_doc_approval_document_package,xf_doc_approval.group_xf_doc_approval_user,1,0,0,0
access_initiator_xf_doc_approval_document_approver,access_initiator_xf_doc_approval_document_approver,model_xf_doc_approval_document_approver,xf_doc_approval.group_xf_doc_approval_initiator,1,1,1,1
access_initiator_xf_doc_approval_document,access_initiator_xf_doc_approval_document,model_xf_doc_approval_document,xf_doc_approval.group_xf_doc_approval_initiator,1,1,1,1
access_initiator_xf_doc_approval_document_package,access_initiator_xf_doc_approval_document_package,model_xf_doc_approval_document_package,xf_doc_approval.group_xf_doc_approval_initiator,1,1,1,1
access_tl_xf_doc_approval_team,access_tl_xf_doc_approval_team,model_xf_doc_approval_team,xf_doc_approval.group_xf_doc_approval_team_leader,1,1,1,1
access_tl_xf_doc_approval_team_approver,access_tl_xf_doc_approval_team_approver,model_xf_doc_approval_team_approver,xf_doc_approval.group_xf_doc_approval_team_leader,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_user_xf_doc_approval_team access_user_xf_doc_approval_team model_xf_doc_approval_team xf_doc_approval.group_xf_doc_approval_user 1 0 0 0
3 access_user_xf_doc_approval_team_approver access_user_xf_doc_approval_team_approver model_xf_doc_approval_team_approver xf_doc_approval.group_xf_doc_approval_user 1 0 0 0
4 access_user_xf_doc_approval_document_approver access_user_xf_doc_approval_document_approver model_xf_doc_approval_document_approver xf_doc_approval.group_xf_doc_approval_user 1 1 0 0
5 access_user_xf_doc_approval_document access_user_xf_doc_approval_document model_xf_doc_approval_document xf_doc_approval.group_xf_doc_approval_user 1 0 0 0
6 access_user_xf_doc_approval_document_package access_user_xf_doc_approval_document_package model_xf_doc_approval_document_package xf_doc_approval.group_xf_doc_approval_user 1 0 0 0
7 access_initiator_xf_doc_approval_document_approver access_initiator_xf_doc_approval_document_approver model_xf_doc_approval_document_approver xf_doc_approval.group_xf_doc_approval_initiator 1 1 1 1
8 access_initiator_xf_doc_approval_document access_initiator_xf_doc_approval_document model_xf_doc_approval_document xf_doc_approval.group_xf_doc_approval_initiator 1 1 1 1
9 access_initiator_xf_doc_approval_document_package access_initiator_xf_doc_approval_document_package model_xf_doc_approval_document_package xf_doc_approval.group_xf_doc_approval_initiator 1 1 1 1
10 access_tl_xf_doc_approval_team access_tl_xf_doc_approval_team model_xf_doc_approval_team xf_doc_approval.group_xf_doc_approval_team_leader 1 1 1 1
11 access_tl_xf_doc_approval_team_approver access_tl_xf_doc_approval_team_approver model_xf_doc_approval_team_approver xf_doc_approval.group_xf_doc_approval_team_leader 1 1 1 1
+203
View File
@@ -0,0 +1,203 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="module_category_xf_doc_approval" model="res.groups.privilege">
<field name="name">Doc Approval</field>
<field name="description">Helps you approve single documents and document packages.</field>
<field name="sequence">15</field>
</record>
<record id="group_xf_doc_approval_user" model="res.groups">
<field name="name">User</field>
<field name="privilege_id" ref="module_category_xf_doc_approval"/>
</record>
<record id="group_xf_doc_approval_initiator" model="res.groups">
<field name="name">Initiator</field>
<field name="privilege_id" ref="module_category_xf_doc_approval"/>
<field name="implied_ids" eval="[(4, ref('group_xf_doc_approval_user'))]"/>
</record>
<record id="group_xf_doc_approval_team_leader" model="res.groups">
<field name="name">Team Leader</field>
<field name="privilege_id" ref="module_category_xf_doc_approval"/>
<field name="implied_ids" eval="[(4, ref('group_xf_doc_approval_initiator'))]"/>
</record>
<record id="group_xf_doc_approval_manager" model="res.groups">
<field name="name">Manager</field>
<field name="privilege_id" ref="module_category_xf_doc_approval"/>
<field name="implied_ids" eval="[(4, ref('group_xf_doc_approval_team_leader'))]"/>
<field name="user_ids" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
<!-- Rules -->
<!-- Company Based Global Rules -->
<record model="ir.rule" id="xf_doc_approval_team_comp_rule">
<field name="name">Approval Team (multi-company)</field>
<field name="model_id" ref="model_xf_doc_approval_team"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record model="ir.rule" id="xf_doc_approval_document_package_comp_rule">
<field name="name">Document Package (multi-company)</field>
<field name="model_id" ref="model_xf_doc_approval_document_package"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record model="ir.rule" id="xf_doc_approval_document_comp_rule">
<field name="name">Document (multi-company)</field>
<field name="model_id" ref="model_xf_doc_approval_document"/>
<field name="domain_force">['|',('document_package_id.company_id','=',False),('document_package_id.company_id', 'in', company_ids)]</field>
</record>
<!-- User Rules -->
<record model="ir.rule" id="model_xf_doc_approval_document_package_user_rule">
<field name="name">User Access for Document Package</field>
<field name="model_id" ref="model_xf_doc_approval_document_package"/>
<field name="domain_force">
[
'|','|','|',
('visibility', '=', 'all_users'),
'&amp;', ('visibility', '=', 'followers'), ('message_partner_ids', 'in', [user.partner_id.id]),
('approver_ids.user_id', '=', user.id),
('initiator_user_id', '=', user.id),
]
</field>
<field name="groups" eval="[(4,ref('xf_doc_approval.group_xf_doc_approval_user'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="0"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="0"/>
</record>
<record model="ir.rule" id="model_xf_doc_approval_document_user_rule">
<field name="name">User Access for Document</field>
<field name="model_id" ref="model_xf_doc_approval_document"/>
<field name="domain_force">
[
'|','|','|',
('document_package_id.visibility', '=', 'all_users'),
'&amp;', ('document_package_id.visibility', '=', 'followers'), ('document_package_id.message_partner_ids', 'in', [user.partner_id.id]),
('document_package_id.approver_ids.user_id', '=', user.id),
('document_package_id.initiator_user_id', '=', user.id),
]
</field>
<field name="groups" eval="[(4,ref('xf_doc_approval.group_xf_doc_approval_user'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="0"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="0"/>
</record>
<record model="ir.rule" id="model_xf_doc_approval_document_approver_user_rule">
<field name="name">User Access for Document Approver</field>
<field name="model_id" ref="model_xf_doc_approval_document_approver"/>
<field name="domain_force">
[
'|','|','|',
('document_package_id.visibility', '=', 'all_users'),
'&amp;', ('document_package_id.visibility', '=', 'followers'), ('document_package_id.message_partner_ids', 'in', [user.partner_id.id]),
('document_package_id.approver_ids.user_id', '=', user.id),
('document_package_id.initiator_user_id', '=', user.id),
]
</field>
<field name="groups" eval="[(4,ref('xf_doc_approval.group_xf_doc_approval_user'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="0"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="0"/>
</record>
<record model="ir.rule" id="model_xf_doc_approval_document_approver_user_rule">
<field name="name">User Access for Document Approver</field>
<field name="model_id" ref="model_xf_doc_approval_document_approver"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="groups" eval="[(4,ref('xf_doc_approval.group_xf_doc_approval_user'))]"/>
<field name="perm_read" eval="0"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="0"/>
</record>
<record model="ir.rule" id="model_xf_doc_approval_document_package_initiator_rule">
<field name="name">Initiator Access for Document Package</field>
<field name="model_id" ref="model_xf_doc_approval_document_package"/>
<field name="domain_force">[('initiator_user_id', '=', user.id)]</field>
<field name="groups" eval="[(4,ref('xf_doc_approval.group_xf_doc_approval_initiator'))]"/>
<field name="perm_read" eval="0"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>
<record model="ir.rule" id="model_xf_doc_approval_document_package_account__user_rule">
<field name="name">Initiator Access for Document Package</field>
<field name="model_id" ref="model_xf_doc_approval_document_package"/>
<field name="domain_force">[]</field>
<field name="groups" eval="[(4,ref('account.group_account_user'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="0"/>
</record>
<record model="ir.rule" id="model_xf_doc_approval_document_package_account_manager_rule">
<field name="name">Initiator Access for Document Package</field>
<field name="model_id" ref="model_xf_doc_approval_document_package"/>
<field name="domain_force">[]</field>
<field name="groups" eval="[(4,ref('account.group_account_manager'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="0"/>
</record>
<record model="ir.rule" id="model_xf_doc_approval_document_initiator_rule">
<field name="name">Initiator Access for Document</field>
<field name="model_id" ref="model_xf_doc_approval_document"/>
<field name="domain_force">[('document_package_id.initiator_user_id', '=', user.id)]</field>
<field name="groups" eval="[(4,ref('xf_doc_approval.group_xf_doc_approval_initiator'))]"/>
<field name="perm_read" eval="0"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>
<record model="ir.rule" id="model_xf_doc_approval_document_approver_initiator_rule">
<field name="name">Initiator Access for Document Approver</field>
<field name="model_id" ref="model_xf_doc_approval_document_approver"/>
<field name="domain_force">[('document_package_id.initiator_user_id', '=', user.id)]</field>
<field name="groups" eval="[(4,ref('xf_doc_approval.group_xf_doc_approval_initiator'))]"/>
<field name="perm_read" eval="0"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>
<record model="ir.rule" id="model_xf_doc_approval_team_tl_rule">
<field name="name">Team Leader Access for Approval Team</field>
<field name="model_id" ref="model_xf_doc_approval_team"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="groups" eval="[(4,ref('xf_doc_approval.group_xf_doc_approval_team_leader'))]"/>
<field name="perm_read" eval="0"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>
<record model="ir.rule" id="model_xf_doc_approval_team_approver_tl_rule">
<field name="name">Team Leader Access for Approval Team Member</field>
<field name="model_id" ref="model_xf_doc_approval_team_approver"/>
<field name="domain_force">[('team_id.user_id', '=', user.id)]</field>
<field name="groups" eval="[(4,ref('xf_doc_approval.group_xf_doc_approval_team_leader'))]"/>
<field name="perm_read" eval="0"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>
</data>
</odoo>
+45
View File
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="action_approve_wizard" model="ir.ui.view">
<field name="name">action_approve_wizard</field>
<field name="model">xf.doc.approval.document.approver</field>
<field name="priority">20</field>
<field name="arch" type="xml">
<form>
<sheet>
<field name="method" invisible="True"/>
<group name="notes">
<field name="notes" readonly="False" placeholder="Some comments"/>
</group>
</sheet>
<footer>
<button name="action_approve" string="Approve" type="object" class="btn-primary"
default_focus="1"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_reject_wizard" model="ir.ui.view">
<field name="name">action_reject_wizard</field>
<field name="model">xf.doc.approval.document.approver</field>
<field name="priority">20</field>
<field name="arch" type="xml">
<form>
<sheet>
<field name="method" invisible="True"/>
<group name="notes">
<field name="notes" required="True" readonly="False" placeholder="Reject reason or comments"/>
</group>
</sheet>
<footer>
<button name="action_reject" string="Reject" type="object" class="btn-primary"
default_focus="1"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
</data>
</odoo>
+126
View File
@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="xf_doc_approval_document_package_form" model="ir.ui.view">
<field name="name">xf_doc_approval_document_package_form</field>
<field name="model">xf.doc.approval.document.package</field>
<field name="arch" type="xml">
<form class="o_sale_order">
<!-- Hidden fields for logic -->
<field name="is_initiator" invisible="True"/>
<field name="is_approver" invisible="True"/>
<header>
<button string="Send for Approval" name="action_send_for_approval" type="object" class="oe_highlight" invisible="state!='draft' or not is_initiator" confirm="Please confirm that you want to send documents for approval"/>
<button string="Approve" name="action_approve_wizard" type="object" class="oe_highlight" invisible="state!='approval' or not is_approver"/>
<button string="Reject" name="action_reject_wizard" type="object" invisible="state!='approval' or not is_approver"/>
<button string="Set to Draft" name="action_draft" type="object" invisible="state not in ['rejected','cancelled'] or not is_initiator"/>
<button string="Cancel" name="action_cancel" type="object" invisible="state!='approval' or not is_initiator" confirm="Please confirm that you want to cancel approval process"/>
<button string="Force Cancel" name="action_cancel" type="object" invisible="state!='approved'" groups="base.group_system"/>
<field name="state" widget="statusbar" statusbar_visible="draft,approval,approved"/>
</header>
<sheet>
<div class="oe_title">
<label for="name"/>
<h1>
<field name="name" placeholder="Request Name" nolabel="1" readonly="not state or state != 'draft'"/>
</h1>
</div>
<group name="approvers" string="Approvers">
<field name="approver_ids" nolabel="1" colspan="2" readonly="not state or state != 'draft'">
<list editable="bottom">
<field name="step"/>
<field name="user_id"/>
<field name="role"/>
<field name="state"/>
<field name="method" invisible="True"/>
<field name="notes"/>
</list>
<form>
<group>
<group>
<field name="user_id"/>
<field name="role"/>
</group>
<group>
<field name="step"/>
<field name="state"/>
<field name="method" invisible="True"/>
</group>
</group>
<group>
<field name="notes"/>
</group>
</form>
</field>
</group>
<group invisible="not is_initiator">
<group name="visibility" string="Visibility">
<field name="company_id" readonly="not state or state != 'draft'" widget="selection"/>
<field name="visibility"/>
</group>
<group name="approval" string="Approval">
<field name="approval_team_id" readonly="not state or state != 'draft'" widget="selection"/>
<field name="method" readonly="not state or state != 'draft'"/>
<field name="approval_state"/>
<field name="approval_step"/>
</group>
</group>
<group name="documents" string="Documents">
<field name="document_ids" nolabel="1" colspan="2" readonly="not state or state != 'draft'">
<list editable="bottom">
<field name="name"/>
<field name="file" widget="binary" filename="file_name"/>
<field name="file_name" column_invisible="True"/>
</list>
<form>
<group>
<field name="name"/>
<field name="file" widget="binary" filename="file_name"/>
<field name="file_name" invisible="True"/>
</group>
</form>
</field>
</group>
<group>
<field name="initiator_user_id" readonly="not state or state != 'draft'"/>
</group>
<group name="description" string="Description" invisible="state!='draft' or not description">
<field name="description" nolabel="1" colspan="2"/>
</group>
</sheet>
<chatter/>
<!-- REMOVED MANUAL CHATTER BLOCK -->
<!-- Odoo 19 automatically adds the Chatter if the model inherits mail.thread -->
</form>
</field>
</record>
<record id="xf_doc_approval_document_package_tree" model="ir.ui.view">
<field name="name">xf_doc_approval_document_package_tree</field>
<field name="model">xf.doc.approval.document.package</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
<field name="initiator_user_id"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="state"/>
</list>
</field>
</record>
<record id="action_xf_doc_approval_document_package" model="ir.actions.act_window">
<field name="name">Documents</field>
<field name="res_model">xf.doc.approval.document.package</field>
</record>
<menuitem
id="menu_xf_doc_approval_document_package"
action="action_xf_doc_approval_document_package"
parent="menu_xf_doc_approval_root"
sequence="1"
/>
</data>
</odoo>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<menuitem id="menu_xf_doc_approval_root" name="Request Approval"
web_icon="xf_doc_approval,static/description/icon.png"
groups="xf_doc_approval.group_xf_doc_approval_user"/>
<menuitem id="menu_xf_doc_approval_configuration" name="Configuration"
parent="menu_xf_doc_approval_root"
groups="xf_doc_approval.group_xf_doc_approval_team_leader,xf_doc_approval.group_xf_doc_approval_manager"
sequence="100"/>
</data>
</odoo>
+60
View File
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="xf_doc_approval_team_form" model="ir.ui.view">
<field name="name">xf_doc_approval_team_form</field>
<field name="model">xf.doc.approval.team</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="name"/>
<field name="active"/>
</group>
<group>
<field name="user_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<group name="approvers" string="Approvers">
<field name="approver_ids" nolabel="1" colspan="2">
<list editable="bottom">
<field name="step"/>
<field name="user_id"/>
<field name="role"/>
</list>
</field>
</group>
</sheet>
</form>
</field>
</record>
<record id="xf_doc_approval_team_tree" model="ir.ui.view">
<field name="name">xf_doc_approval_team_tree</field>
<field name="model">xf.doc.approval.team</field>
<field name="arch" type="xml">
<list>
<field name="active" invisible="True"/>
<field name="name"/>
<field name="user_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</list>
</field>
</record>
<record id="action_xf_doc_approval_team" model="ir.actions.act_window">
<field name="name">Approval Teams</field>
<field name="res_model">xf.doc.approval.team</field>
</record>
<menuitem
id="menu_xf_doc_approval_team"
action="action_xf_doc_approval_team"
parent="menu_xf_doc_approval_configuration"
sequence="10"
/>
</data>
</odoo>
+24
View File
@@ -0,0 +1,24 @@
<odoo>
<data>
<!--
<template id="listing">
<ul>
<li t-foreach="objects" t-as="object">
<a t-attf-href="#{ root }/objects/#{ object.id }">
<t t-esc="object.display_name"/>
</a>
</li>
</ul>
</template>
<template id="object">
<h1><t t-esc="object.display_name"/></h1>
<dl>
<t t-foreach="object._fields" t-as="field">
<dt><t t-esc="field"/></dt>
<dd><t t-esc="object[field]"/></dd>
</t>
</dl>
</template>
-->
</data>
</odoo>
+60
View File
@@ -0,0 +1,60 @@
<odoo>
<data>
<!-- explicit list view definition -->
<!--
<record model="ir.ui.view" id="xf_doc_approval.list">
<field name="name">xf_doc_approval list</field>
<field name="model">xf_doc_approval.xf_doc_approval</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
<field name="value"/>
<field name="value2"/>
</list>
</field>
</record>
-->
<!-- actions opening views on models -->
<!--
<record model="ir.actions.act_window" id="xf_doc_approval.action_window">
<field name="name">xf_doc_approval window</field>
<field name="res_model">xf_doc_approval.xf_doc_approval</field>
<field name="view_mode">list,form</field>
</record>
-->
<!-- server action to the one above -->
<!--
<record model="ir.actions.server" id="xf_doc_approval.action_server">
<field name="name">xf_doc_approval server</field>
<field name="model_id" ref="model_xf_doc_approval_xf_doc_approval"/>
<field name="state">code</field>
<field name="code">
action = {
"type": "ir.actions.act_window",
"view_mode": "list,form",
"res_model": model._name,
}
</field>
</record>
-->
<!-- Top menu item -->
<!--
<menuitem name="xf_doc_approval" id="xf_doc_approval.menu_root"/>
-->
<!-- menu categories -->
<!--
<menuitem name="Menu 1" id="xf_doc_approval.menu_1" parent="xf_doc_approval.menu_root"/>
<menuitem name="Menu 2" id="xf_doc_approval.menu_2" parent="xf_doc_approval.menu_root"/>
-->
<!-- actions -->
<!--
<menuitem name="List" id="xf_doc_approval.menu_1_list" parent="xf_doc_approval.menu_1"
action="xf_doc_approval.action_window"/>
<menuitem name="Server to list" id="xf_doc_approval" parent="xf_doc_approval.menu_2"
action="xf_doc_approval.action_server"/>
-->
</data>
</odoo>