fix: employee, payroll, resignation, recruitment

This commit is contained in:
pranaysaidurga 2026-07-09 14:43:41 +05:30
parent 42e022bb7f
commit 23e8323d6c
17 changed files with 796 additions and 9 deletions

View File

@ -11,3 +11,23 @@ class ResConfigSettings(models.TransientModel):
domain=lambda self: [
('groups_id', 'in', self.env.ref('hr.group_hr_manager').id)])
notice_period = fields.Boolean(string='Notice Period',
help='Enable to configure a notice period'
' for an employee.',
config_parameter='hr_employee_extended.notice_period')
no_of_days = fields.Integer(string='Notice Period Days',
help='Set the number of days for the notice'
' period.',
config_parameter='hr_employee_extended.no_of_days')
emp_it_manager = fields.Many2one('res.users',config_parameter='hr_employee_extended.emp_it_manager', string='Employee IT Manager',
domain=lambda self: [
('groups_id', 'in', self.env.ref('base.group_user').id)])
emp_finance_manager = fields.Many2one('res.users',config_parameter='hr_employee_extended.emp_finance_manager', string='Employee Finance Manager',
domain=lambda self: [
('groups_id', 'in', self.env.ref('base.group_user').id)])
emp_admin = fields.Many2one('res.users',config_parameter='hr_employee_extended.emp_admin', string='Employee Admin',
domain=lambda self: [
('groups_id', 'in', self.env.ref('base.group_user').id)])

View File

@ -14,6 +14,40 @@
<field name="emp_hr_id"
options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
</setting>
<setting string="Employee Finance Manager Access"
help="Select the Finance Manager responsible for employees Finances"
id="employee_finance_manager_access_control">
<field name="emp_finance_manager"
options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
</setting>
<setting string="Employee IT Manager Access"
help="Select the IT Manager responsible for employees Assets"
id="employee_it_manager_access_control">
<field name="emp_it_manager"
options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
</setting>
<setting string="Admin Access"
help="Select the Admin responsible for employees Creations"
id="employee_admin_access_control">
<field name="emp_admin"
options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
</setting>
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="notice_period"/>
</div>
<div class="o_setting_right_pane">
<label for="notice_period"/>
<div class="content-group" invisible="not notice_period">
<div class="mt16">
<field name="no_of_days" style="width:50px;"/>
<span>days</span>
</div>
</div>
</div>
</div>
</block>
</xpath>

View File

@ -12,13 +12,17 @@
],
'data': [
'data/hr_salary_advance_sequence.xml',
'security/hr_payroll_finance_security.xml',
'security/ir.model.access.csv',
'report/finance_payment_list_report.xml',
'views/hr_payslip_employees_views.xml',
'views/hr_salary_advance_views.xml',
'views/hr_payroll_edit_payslip_lines_wizard_views.xml',
'views/menus.xml',
'views/hr_work_entry_views.xml',
'views/hr_payroll_finance_views.xml',
'views/hr_payslip_refund_views.xml',
'wizard/hr_payslip_finance_reject_wizard_views.xml',
'wizard/hr_payslip_refund_wizard_views.xml',
],
'assets': {

View File

@ -1,11 +1,47 @@
import base64
import io
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo.tools.misc import format_date
from odoo.exceptions import UserError, ValidationError
from odoo.tools.misc import format_amount, format_date, xlsxwriter
class HrPayslip(models.Model):
_inherit = "hr.payslip"
@api.model
def default_get(self, default_fields):
"""If we're creating a new account through a many2one, there are chances that we typed the account code
instead of its name. In that case, switch both fields values.
"""
context = {}
defaults = super(HrPayslip, self).default_get(default_fields)
if 'finance_manager_user_id' in default_fields:
finance_manager = self.env['ir.config_parameter'].sudo().get_param('hr_employee_extended.emp_finance_manager')
if finance_manager:
defaults['finance_manager_user_id'] = int(finance_manager)
return defaults
finance_approval_state = fields.Selection([
("not_sent", "Not Sent"),
("pending", "Pending Finance"),
("approved", "Approved"),
("rejected", "Rejected"),
], string="Finance Approval", default="not_sent", copy=False, tracking=True)
finance_manager_user_id = fields.Many2one("res.users", string="Finance Manager", copy=False, tracking=True)
finance_submitted_date = fields.Datetime(string="Sent to Finance On", copy=False, readonly=True)
finance_approved_date = fields.Datetime(string="Finance Approved On", copy=False, readonly=True)
finance_rejected_date = fields.Datetime(string="Finance Rejected On", copy=False, readonly=True)
finance_rejection_reason = fields.Text(string="Finance Rejection Reason", copy=False, readonly=True)
can_finance_approve = fields.Boolean(compute="_compute_can_finance_approve")
def action_payslip_cancel(self):
if not self.env.user.has_group("hr_payroll_extended.group_payroll_finance_approver") and not self.env.user._is_system() and self.filtered(lambda slip: slip.state == 'done'):
raise UserError(_("Cannot cancel a payslip that is done."))
self.write({'state': 'cancel'})
self.mapped('payslip_run_id').action_close()
@api.model_create_multi
def create(self, vals_list):
payslips = super().create(vals_list)
@ -18,3 +54,196 @@ class HrPayslip(models.Model):
self._check_duplicate_payslip_periods()
return res
@api.depends("finance_manager_user_id", "finance_approval_state", "state")
def _compute_can_finance_approve(self):
for payslip in self:
payslip.can_finance_approve = (
payslip.state == "done"
and payslip.finance_approval_state == "pending"
and payslip.finance_manager_user_id == self.env.user
)
def _get_config_user(self, param_name, error_message):
user_id = self.env["ir.config_parameter"].sudo().get_param(param_name)
if not user_id:
raise UserError(error_message)
user = self.env["res.users"].sudo().browse(int(user_id)).exists()
if not user:
raise UserError(error_message)
return user
def _get_finance_manager(self):
user = self._get_config_user(
"hr_employee_extended.emp_finance_manager",
_("Set the finance manager in employee settings."),
)
finance_group = self.env.ref("hr_payroll_extended.group_payroll_finance_approver", raise_if_not_found=False)
if finance_group and finance_group not in user.groups_id:
user.sudo().write({"groups_id": [(4, finance_group.id)]})
return user
def _get_employee_hr(self):
return self._get_config_user(
"hr_employee_extended.emp_hr_id",
_("Set the HR user in employee settings."),
)
def _send_payroll_mail(self, user, subject, body):
if not user.email:
raise UserError(_("Please configure an email address for %s.", user.name))
self.env["mail.mail"].sudo().create({
"subject": subject,
"body_html": body,
"email_to": user.email,
"auto_delete": True,
}).send()
def _send_finance_submission_mail(self, finance_manager):
for payslip in self:
subject = _("Payslip pending finance approval: %s", payslip.display_name)
body = _(
"<p>Dear %(finance)s,</p>"
"<p>The payslip for %(employee)s (%(period)s) has been validated by HR and is pending your confirmation.</p>"
"<p>Amount: %(amount)s</p>",
finance=finance_manager.name,
employee=payslip.employee_id.name,
period="%s - %s" % (format_date(self.env, payslip.date_from), format_date(self.env, payslip.date_to)),
amount=format_amount(self.env, payslip.net_wage, payslip.currency_id) if payslip.currency_id else payslip.net_wage,
)
payslip._send_payroll_mail(finance_manager, subject, body)
def _send_hr_finance_result_mail(self, approved=True):
hr_user = self._get_employee_hr()
for payslip in self:
if approved:
subject = _("Payslip paid by finance: %s", payslip.display_name)
body = _(
"<p>Dear %(hr)s,</p>"
"<p>The finance manager confirmed payment for %(employee)s (%(period)s).</p>",
hr=hr_user.name,
employee=payslip.employee_id.name,
period="%s - %s" % (format_date(self.env, payslip.date_from), format_date(self.env, payslip.date_to)),
)
else:
subject = _("Payslip rejected by finance: %s", payslip.display_name)
body = _(
"<p>Dear %(hr)s,</p>"
"<p>The finance manager rejected the payslip for %(employee)s (%(period)s).</p>"
"<p><strong>Reason:</strong> %(reason)s</p>",
hr=hr_user.name,
employee=payslip.employee_id.name,
period="%s - %s" % (format_date(self.env, payslip.date_from), format_date(self.env, payslip.date_to)),
reason=payslip.finance_rejection_reason or "",
)
payslip._send_payroll_mail(hr_user, subject, body)
def _mark_pending_finance_approval(self):
finance_manager = self._get_finance_manager()
now = fields.Datetime.now()
slips = self.filtered(lambda slip: slip.state == "done" and not slip.payslip_run_id)
if not slips:
return
slips.write({
"finance_approval_state": "pending",
"finance_manager_user_id": finance_manager.id,
"finance_submitted_date": now,
"finance_approved_date": False,
"finance_rejected_date": False,
"finance_rejection_reason": False,
})
slips.mapped("payslip_run_id").write({
"finance_manager_user_id": finance_manager.id,
"finance_approval_state": "pending",
"finance_submitted_date": now,
"finance_approved_date": False,
"finance_rejected_date": False,
"finance_rejection_reason": False,
})
slips._send_finance_submission_mail(finance_manager)
def action_payslip_done(self):
result = super().action_payslip_done()
self._mark_pending_finance_approval()
return result
def _check_current_user_is_finance_manager(self):
finance_manager = self._get_finance_manager()
if self.env.user != finance_manager and not self.env.user._is_system():
raise UserError(_("Only the configured finance manager can confirm or reject these payslips."))
def action_payslip_paid(self):
self._check_current_user_is_finance_manager()
pending_slips = self.filtered(lambda slip: slip.state != "paid")
if any(slip.finance_approval_state != "pending" for slip in pending_slips):
raise UserError(_("Only payslips pending finance approval can be marked as paid."))
result = super().action_payslip_paid()
approved_slips = self.filtered(lambda slip: slip.state == "paid")
approved_slips.write({
"finance_approval_state": "approved",
"finance_approved_date": fields.Datetime.now(),
})
approved_slips._send_hr_finance_result_mail(approved=True)
return result
def action_finance_reject_wizard(self):
self.ensure_one()
self._check_current_user_is_finance_manager()
if self.state != "done" or self.finance_approval_state != "pending":
raise UserError(_("Only payslips pending finance approval can be rejected."))
return {
"type": "ir.actions.act_window",
"name": _("Reject Payslip"),
"res_model": "hr.payslip.finance.reject.wizard",
"view_mode": "form",
"target": "new",
"context": {"default_payslip_ids": [(6, 0, self.ids)]},
}
def action_finance_download_xlsx(self):
return self._action_finance_download_xlsx()
def action_finance_download_pdf(self):
return self.env.ref("hr_payroll_extended.action_report_finance_payment_list").report_action(self)
def _get_finance_payment_rows(self):
rows = []
for slip in self:
rows.append({
"employee": slip.employee_id.name or "",
"bank_account": slip.employee_id.bank_account_id.acc_number or "",
"amount": slip.net_wage,
"currency": slip.currency_id,
})
return rows
def _action_finance_download_xlsx(self, filename=None):
if not self:
raise UserError(_("There are no payslips to export."))
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {"in_memory": True})
worksheet = workbook.add_worksheet("Payment List")
header_format = workbook.add_format({"bold": True, "bg_color": "#E0E0E0", "align": "center"})
money_format = workbook.add_format({"num_format": "#,##0.00"})
headers = [_("Employee Name"), _("Bank Account"), _("Amount")]
worksheet.write_row(0, 0, headers, header_format)
for row_idx, row in enumerate(self._get_finance_payment_rows(), start=1):
worksheet.write(row_idx, 0, row["employee"])
worksheet.write(row_idx, 1, row["bank_account"])
worksheet.write_number(row_idx, 2, row["amount"] or 0.0, money_format)
worksheet.set_column(0, 0, 28)
worksheet.set_column(1, 1, 26)
worksheet.set_column(2, 2, 16)
workbook.close()
attachment = self.env["ir.attachment"].sudo().create({
"name": filename or _("Finance Payment List.xlsx"),
"type": "binary",
"datas": base64.b64encode(output.getvalue()),
"mimetype": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"res_model": self._name,
"res_id": self[:1].id,
})
return {
"type": "ir.actions.act_url",
"url": "/web/content/%s?download=true" % attachment.id,
"target": "self",
}

View File

@ -1,11 +1,41 @@
from dateutil.relativedelta import relativedelta
from odoo import api, models
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class HrPayslipRun(models.Model):
_inherit = "hr.payslip.run"
@api.model
def default_get(self, default_fields):
"""If we're creating a new account through a many2one, there are chances that we typed the account code
instead of its name. In that case, switch both fields values.
"""
context = {}
defaults = super(HrPayslipRun, self).default_get(default_fields)
if 'finance_manager_user_id' in default_fields:
finance_manager = self.env['ir.config_parameter'].sudo().get_param(
'hr_employee_extended.emp_finance_manager')
if finance_manager:
defaults['finance_manager_user_id'] = int(finance_manager)
return defaults
finance_approval_state = fields.Selection([
("not_sent", "Not Sent"),
("pending", "Pending Finance"),
("approved", "Approved"),
("rejected", "Rejected"),
], string="Finance Approval", default="not_sent", copy=False, tracking=True)
finance_manager_user_id = fields.Many2one("res.users", string="Finance Manager", copy=False, tracking=True)
finance_submitted_date = fields.Datetime(string="Sent to Finance On", copy=False, readonly=True)
finance_approved_date = fields.Datetime(string="Finance Approved On", copy=False, readonly=True)
finance_rejected_date = fields.Datetime(string="Finance Rejected On", copy=False, readonly=True)
finance_rejection_reason = fields.Text(string="Finance Rejection Reason", copy=False, readonly=True)
can_finance_approve = fields.Boolean(compute="_compute_can_finance_approve")
@api.onchange("date_start")
def _onchange_date_start_extended(self):
for payslip_run in self:
@ -16,3 +46,81 @@ class HrPayslipRun(models.Model):
else:
payslip_run.date_end = payslip_run.date_start + relativedelta(months=1)
@api.depends("finance_manager_user_id", "finance_approval_state", "state")
def _compute_can_finance_approve(self):
for payslip_run in self:
payslip_run.can_finance_approve = (
payslip_run.state == "close"
and payslip_run.finance_approval_state == "pending"
and payslip_run.finance_manager_user_id == self.env.user
)
def _send_finance_submission_mail(self, finance_manager):
for payslip_run in self:
subject = _("Payslip batch pending finance approval: %s", payslip_run.name)
body = _(
"<p>Dear %(finance)s,</p>"
"<p>The payslip batch %(batch)s has been validated by HR and is pending your confirmation.</p>"
"<p>Payslips: %(count)s</p>",
finance=finance_manager.name,
batch=payslip_run.name,
count=len(payslip_run.slip_ids),
)
payslip_run.slip_ids[:1]._send_payroll_mail(finance_manager, subject, body)
def action_validate(self):
result = super().action_validate()
for payslip_run in self.filtered(lambda run: run.state == "close"):
finance_manager = payslip_run.slip_ids[:1]._get_finance_manager()
now = fields.Datetime.now()
payslip_run.write({
"finance_approval_state": "pending",
"finance_manager_user_id": finance_manager.id,
"finance_submitted_date": now,
"finance_approved_date": False,
"finance_rejected_date": False,
"finance_rejection_reason": False,
})
payslip_run.slip_ids.filtered(lambda slip: slip.state == "done").write({
"finance_approval_state": "pending",
"finance_manager_user_id": finance_manager.id,
"finance_submitted_date": now,
"finance_approved_date": False,
"finance_rejected_date": False,
"finance_rejection_reason": False,
})
payslip_run._send_finance_submission_mail(finance_manager)
return result
def action_paid(self):
for payslip_run in self:
if payslip_run.finance_approval_state != "pending":
raise UserError(_("Only batches pending finance approval can be marked as paid."))
result = super().action_paid()
self.write({
"finance_approval_state": "approved",
"finance_approved_date": fields.Datetime.now(),
})
return result
def action_finance_reject_wizard(self):
self.ensure_one()
self.slip_ids[:1]._check_current_user_is_finance_manager()
if self.state != "close" or self.finance_approval_state != "pending":
raise UserError(_("Only batches pending finance approval can be rejected."))
return {
"type": "ir.actions.act_window",
"name": _("Reject Payslip Batch"),
"res_model": "hr.payslip.finance.reject.wizard",
"view_mode": "form",
"target": "new",
"context": {"default_payslip_run_id": self.id},
}
def action_finance_download_xlsx(self):
self.ensure_one()
return self.slip_ids._action_finance_download_xlsx(filename="%s.xlsx" % (self.name or _("Finance Payment List")))
def action_finance_download_pdf(self):
self.ensure_one()
return self.env.ref("hr_payroll_extended.action_report_finance_payment_list").report_action(self.slip_ids)

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_report_finance_payment_list" model="ir.actions.report">
<field name="name">Finance Payment List</field>
<field name="model">hr.payslip</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">hr_payroll_extended.report_finance_payment_list</field>
<field name="report_file">hr_payroll_extended.report_finance_payment_list</field>
<field name="print_report_name">'Finance Payment List'</field>
</record>
<template id="report_finance_payment_list">
<t t-call="web.html_container">
<t t-call="web.external_layout">
<div class="page">
<h2>Finance Payment List</h2>
<table class="table table-sm">
<thead>
<tr>
<th>Employee Name</th>
<th>Bank Account</th>
<th class="text-end">Amount</th>
</tr>
</thead>
<tbody>
<t t-set="total" t-value="0.0"/>
<tr t-foreach="docs" t-as="slip">
<t t-set="total" t-value="total + (slip.net_wage or 0.0)"/>
<td>
<span t-field="slip.employee_id.name"/>
</td>
<td>
<span t-field="slip.employee_id.bank_account_id.acc_number"/>
</td>
<td class="text-end">
<span t-out="slip.net_wage"
t-options="{'widget': 'monetary', 'display_currency': slip.currency_id}"/>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<th colspan="2" class="text-end">Total</th>
<th class="text-end">
<span t-if="docs"
t-out="total"
t-options="{'widget': 'monetary', 'display_currency': docs[0].currency_id}"/>
</th>
</tr>
</tfoot>
</table>
</div>
</t>
</t>
</template>
</odoo>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="group_payroll_finance_approver" model="res.groups">
<field name="name">Payroll Finance Approver</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<!-- <record id="rule_hr_payslip_finance_manager" model="ir.rule">-->
<!-- <field name="name">Finance Manager Payslips</field>-->
<!-- <field name="model_id" ref="hr_payroll.model_hr_payslip"/>-->
<!-- <field name="domain_force">[('finance_manager_user_id', '=', user.id),('state','=','close')]</field>-->
<!-- <field name="groups" eval="[(4, ref('hr_payroll_extended.group_payroll_finance_approver'))]"/>-->
<!-- <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 id="rule_hr_payslip_run_finance_manager" model="ir.rule">-->
<!-- <field name="name">Finance Manager Payslip Batches</field>-->
<!-- <field name="model_id" ref="hr_payroll.model_hr_payslip_run"/>-->
<!-- <field name="domain_force">[('finance_manager_user_id', '=', user.id),('state','=','close')]</field>-->
<!-- <field name="groups" eval="[(4, ref('hr_payroll_extended.group_payroll_finance_approver'))]"/>-->
<!-- <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 id="rule_hr_payslip_line_finance_manager" model="ir.rule">-->
<!-- <field name="name">Finance Manager Payslip Lines</field>-->
<!-- <field name="model_id" ref="hr_payroll.model_hr_payslip_line"/>-->
<!-- <field name="domain_force">[('slip_id.finance_manager_user_id', '=', user.id),('slip_id.state','=','done')]</field>-->
<!-- <field name="groups" eval="[(4, ref('hr_payroll_extended.group_payroll_finance_approver'))]"/>-->
<!-- <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 id="rule_hr_payslip_input_finance_manager" model="ir.rule">-->
<!-- <field name="name">Finance Manager Payslip Inputs</field>-->
<!-- <field name="model_id" ref="hr_payroll.model_hr_payslip_input"/>-->
<!-- <field name="domain_force">[('payslip_id.finance_manager_user_id', '=', user.id),('payslip_id.state','=','done')]</field>-->
<!-- <field name="groups" eval="[(4, ref('hr_payroll_extended.group_payroll_finance_approver'))]"/>-->
<!-- <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 id="rule_hr_payslip_worked_days_finance_manager" model="ir.rule">-->
<!-- <field name="name">Finance Manager Payslip Worked Days</field>-->
<!-- <field name="model_id" ref="hr_payroll.model_hr_payslip_worked_days"/>-->
<!-- <field name="domain_force">[('payslip_id.finance_manager_user_id', '=', user.id),('payslip_id.state','=','done')]</field>-->
<!-- <field name="groups" eval="[(4, ref('hr_payroll_extended.group_payroll_finance_approver'))]"/>-->
<!-- <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>-->
</data>
</odoo>

View File

@ -2,3 +2,9 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_salary_advance_user,hr.salary.advance.user,model_hr_salary_advance,hr_payroll.group_hr_payroll_user,1,1,1,0
access_hr_salary_advance_manager,hr.salary.advance.manager,model_hr_salary_advance,hr_payroll.group_hr_payroll_manager,1,1,1,1
access_hr_payslip_refund_wizard,hr.payslip.refund.wizard,model_hr_payslip_refund_wizard,hr_payroll.group_hr_payroll_user,1,1,1,1
access_hr_payslip_finance_user,hr.payslip.finance.user,hr_payroll.model_hr_payslip,hr_payroll_extended.group_payroll_finance_approver,1,1,0,0
access_hr_payslip_run_finance_user,hr.payslip.run.finance.user,hr_payroll.model_hr_payslip_run,hr_payroll_extended.group_payroll_finance_approver,1,1,0,0
access_hr_payslip_line_finance_user,hr.payslip.line.finance.user,hr_payroll.model_hr_payslip_line,hr_payroll_extended.group_payroll_finance_approver,1,0,0,0
access_hr_payslip_input_finance_user,hr.payslip.input.finance.user,hr_payroll.model_hr_payslip_input,hr_payroll_extended.group_payroll_finance_approver,1,0,0,0
access_hr_payslip_worked_days_finance_user,hr.payslip.worked.days.finance.user,hr_payroll.model_hr_payslip_worked_days,hr_payroll_extended.group_payroll_finance_approver,1,0,0,0
access_hr_payslip_finance_reject_wizard,hr.payslip.finance.reject.wizard,model_hr_payslip_finance_reject_wizard,hr_payroll_extended.group_payroll_finance_approver,1,1,1,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_hr_salary_advance_user hr.salary.advance.user model_hr_salary_advance hr_payroll.group_hr_payroll_user 1 1 1 0
3 access_hr_salary_advance_manager hr.salary.advance.manager model_hr_salary_advance hr_payroll.group_hr_payroll_manager 1 1 1 1
4 access_hr_payslip_refund_wizard hr.payslip.refund.wizard model_hr_payslip_refund_wizard hr_payroll.group_hr_payroll_user 1 1 1 1
5 access_hr_payslip_finance_user hr.payslip.finance.user hr_payroll.model_hr_payslip hr_payroll_extended.group_payroll_finance_approver 1 1 0 0
6 access_hr_payslip_run_finance_user hr.payslip.run.finance.user hr_payroll.model_hr_payslip_run hr_payroll_extended.group_payroll_finance_approver 1 1 0 0
7 access_hr_payslip_line_finance_user hr.payslip.line.finance.user hr_payroll.model_hr_payslip_line hr_payroll_extended.group_payroll_finance_approver 1 0 0 0
8 access_hr_payslip_input_finance_user hr.payslip.input.finance.user hr_payroll.model_hr_payslip_input hr_payroll_extended.group_payroll_finance_approver 1 0 0 0
9 access_hr_payslip_worked_days_finance_user hr.payslip.worked.days.finance.user hr_payroll.model_hr_payslip_worked_days hr_payroll_extended.group_payroll_finance_approver 1 0 0 0
10 access_hr_payslip_finance_reject_wizard hr.payslip.finance.reject.wizard model_hr_payslip_finance_reject_wizard hr_payroll_extended.group_payroll_finance_approver 1 1 1 0

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_hr_payslip_tree_finance_inherit" model="ir.ui.view">
<field name="name">hr.payslip.list.finance.inherit</field>
<field name="model">hr.payslip</field>
<field name="inherit_id" ref="hr_payroll.view_hr_payslip_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='net_wage']" position="after">
<field name="finance_approval_state" optional="show" widget="badge"
decoration-warning="finance_approval_state == 'pending'"
decoration-success="finance_approval_state == 'approved'"
decoration-danger="finance_approval_state == 'rejected'"/>
<field name="finance_manager_user_id" optional="hide"/>
</xpath>
</field>
</record>
<record id="view_hr_payslip_form_finance_inherit" model="ir.ui.view">
<field name="name">hr.payslip.form.finance.inherit</field>
<field name="model">hr.payslip</field>
<field name="inherit_id" ref="hr_payroll.view_hr_payslip_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_payslip_paid']" position="attributes">
<attribute name="invisible">not can_finance_approve</attribute>
<attribute name="string">Confirm Payment</attribute>
</xpath>
<xpath expr="//button[@name='action_payslip_payment_report']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='action_payslip_paid']" position="after">
<button string="Reject" name="action_finance_reject_wizard" type="object"
invisible="not can_finance_approve"/>
<button string="Download Excel" name="action_finance_download_xlsx" type="object"
invisible="finance_approval_state not in ('pending', 'approved')"/>
<button string="Download PDF" name="action_finance_download_pdf" type="object"
invisible="finance_approval_state not in ('pending', 'approved')"/>
</xpath>
<xpath expr="//field[@name='has_refund_slip']" position="after">
<field name="can_finance_approve" invisible="1"/>
<field name="finance_approval_state" readonly="1"/>
<field name="finance_manager_user_id" readonly="1"/>
<field name="finance_submitted_date" readonly="1" invisible="not finance_submitted_date"/>
<field name="finance_approved_date" readonly="1" invisible="not finance_approved_date"/>
<field name="finance_rejected_date" readonly="1" invisible="not finance_rejected_date"/>
<field name="finance_rejection_reason" readonly="1" invisible="not finance_rejection_reason"/>
</xpath>
</field>
</record>
<record id="view_hr_payslip_run_tree_finance_inherit" model="ir.ui.view">
<field name="name">hr.payslip.run.list.finance.inherit</field>
<field name="model">hr.payslip.run</field>
<field name="inherit_id" ref="hr_payroll.hr_payslip_run_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='state']" position="after">
<field name="finance_approval_state" widget="badge"
decoration-warning="finance_approval_state == 'pending'"
decoration-success="finance_approval_state == 'approved'"
decoration-danger="finance_approval_state == 'rejected'"/>
<field name="finance_manager_user_id" optional="hide"/>
</xpath>
</field>
</record>
<record id="view_hr_payslip_run_form_finance_inherit" model="ir.ui.view">
<field name="name">hr.payslip.run.form.finance.inherit</field>
<field name="model">hr.payslip.run</field>
<field name="inherit_id" ref="hr_payroll.hr_payslip_run_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_paid']" position="attributes">
<attribute name="invisible">not can_finance_approve</attribute>
<attribute name="string">Confirm Payment</attribute>
</xpath>
<xpath expr="//button[@name='action_payment_report']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='action_paid']" position="after">
<button string="Reject" name="action_finance_reject_wizard" type="object"
invisible="not can_finance_approve"/>
<button string="Download Excel" name="action_finance_download_xlsx" type="object"
invisible="finance_approval_state not in ('pending', 'approved')"/>
<button string="Download PDF" name="action_finance_download_pdf" type="object"
invisible="finance_approval_state not in ('pending', 'approved')"/>
</xpath>
<xpath expr="//field[@name='country_code']" position="after">
<field name="can_finance_approve" invisible="1"/>
<field name="finance_approval_state" readonly="1"/>
<field name="finance_manager_user_id" readonly="1"/>
<field name="finance_submitted_date" readonly="1" invisible="not finance_submitted_date"/>
<field name="finance_approved_date" readonly="1" invisible="not finance_approved_date"/>
<field name="finance_rejected_date" readonly="1" invisible="not finance_rejected_date"/>
<field name="finance_rejection_reason" readonly="1" invisible="not finance_rejection_reason"/>
</xpath>
</field>
</record>
<record id="action_finance_payslip_batches" model="ir.actions.act_window">
<field name="name">Payroll Finance Approvals</field>
<field name="res_model">hr.payslip.run</field>
<field name="view_mode">list,form</field>
<field name="domain">[('finance_approval_state', 'in', ('pending', 'approved', 'rejected'))]</field>
<field name="context">{'search_default_state': 1}</field>
</record>
<record id="action_finance_payslips" model="ir.actions.act_window">
<field name="name">Finance Payslips</field>
<field name="res_model">hr.payslip</field>
<field name="view_mode">list,form</field>
<field name="domain">[('finance_approval_state', 'in', ('pending', 'approved', 'rejected'))]</field>
</record>
<menuitem id="menu_payroll_finance_root" name="Payroll Finance" sequence="195" active="0" groups="hr_payroll_extended.group_payroll_finance_approver"/>
<menuitem id="menu_payroll_finance_batches" name="Batches" parent="menu_payroll_finance_root" active="0"
action="action_finance_payslip_batches" sequence="10" groups="hr_payroll_extended.group_payroll_finance_approver"/>
<menuitem id="menu_payroll_finance_payslips" name="Payslips" parent="menu_payroll_finance_root" active="0"
action="action_finance_payslips" sequence="20" groups="hr_payroll_extended.group_payroll_finance_approver"/>
</odoo>

View File

@ -1,2 +1,3 @@
from . import hr_payroll_edit_payslip_lines_wizard
from . import hr_payslip_finance_reject_wizard
from . import hr_payslip_refund_wizard

View File

@ -0,0 +1,38 @@
from odoo import fields, models, _
from odoo.exceptions import UserError
class HrPayslipFinanceRejectWizard(models.TransientModel):
_name = "hr.payslip.finance.reject.wizard"
_description = "Reject Payslip Finance Approval"
payslip_run_id = fields.Many2one("hr.payslip.run", string="Payslip Batch", readonly=True)
payslip_ids = fields.Many2many("hr.payslip", string="Payslips", readonly=True)
reason = fields.Text(required=True)
def action_reject(self):
self.ensure_one()
payslips = self.payslip_ids
if self.payslip_run_id:
payslips = self.payslip_run_id.slip_ids.filtered(lambda slip: slip.state == "done")
if not payslips:
raise UserError(_("There are no payslips to reject."))
payslips[:1]._check_current_user_is_finance_manager()
invalid_payslips = payslips.filtered(lambda slip: slip.finance_approval_state != "pending")
if invalid_payslips:
raise UserError(_("Only payslips pending finance approval can be rejected."))
now = fields.Datetime.now()
payslips.write({
"finance_approval_state": "rejected",
"finance_rejected_date": now,
"finance_rejection_reason": self.reason,
})
if self.payslip_run_id:
self.payslip_run_id.write({
"finance_approval_state": "rejected",
"finance_rejected_date": now,
"finance_rejection_reason": self.reason,
})
payslips._send_hr_finance_result_mail(approved=False)
return {"type": "ir.actions.act_window_close"}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_hr_payslip_finance_reject_wizard_form" model="ir.ui.view">
<field name="name">hr.payslip.finance.reject.wizard.form</field>
<field name="model">hr.payslip.finance.reject.wizard</field>
<field name="arch" type="xml">
<form string="Reject Finance Approval">
<group>
<field name="payslip_run_id" invisible="not payslip_run_id"/>
<field name="payslip_ids" widget="many2many_tags" invisible="payslip_run_id"/>
<field name="reason" placeholder="Enter rejection reason..."/>
</group>
<footer>
<button name="action_reject" string="Reject" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@ -42,7 +42,7 @@
'views/hr_resignation_views.xml',
# 'views/hr_employee_views.xml',
'views/pre_resignation_requirements_proceedings.xml',
'views/res_config_settings_views.xml',
# 'views/res_config_settings_views.xml',
],
'images': ['static/description/banner.jpg'],
'license': 'LGPL-3',

View File

@ -20,7 +20,7 @@
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from . import res_config_settings
# from . import res_config_settings
from . import hr_contract
from . import hr_resignation
from . import hr_employee

View File

@ -46,8 +46,8 @@ class HrResignation(models.Model):
"""
context = {}
if 'notice_period' in default_fields:
notice_period = self.env['ir.config_parameter'].sudo().get_param('hr_resignation.notice_period')
no_of_days = self.env['ir.config_parameter'].sudo().get_param('hr_resignation.no_of_days')
notice_period = self.env['ir.config_parameter'].sudo().get_param('hr_employee_extended.notice_period')
no_of_days = self.env['ir.config_parameter'].sudo().get_param('hr_employee_extended.no_of_days')
defaults = super(HrResignation, self).default_get(default_fields)
if notice_period == 'True':

View File

@ -31,11 +31,11 @@ class ResConfigSettings(models.TransientModel):
notice_period = fields.Boolean(string='Notice Period',
help='Enable to configure a notice period'
' for an employee.',
config_parameter='hr_resignation.notice_period')
config_parameter='hr_employee_extended.notice_period')
no_of_days = fields.Integer(string='Notice Period Days',
help='Set the number of days for the notice'
' period.',
config_parameter='hr_resignation.no_of_days')
config_parameter='hr_employee_extended.no_of_days')
emp_it_manager = fields.Many2one('res.users',config_parameter='hr_employee_extended.emp_it_manager', string='Employee IT Manager',
domain=lambda self: [
('groups_id', 'in', self.env.ref('base.group_user').id)])

View File

@ -118,6 +118,96 @@
</t>
</template>
<record id="website_hr_recruitment.thankyou" model="website.page">
<field name="url">/job-thank-you</field>
<field name="is_published">True</field>
<field name="website_indexed" eval="False"/>
<field name="name">Thank you (Recruitment)</field>
<field name="type">qweb</field>
<field name="key">website_hr_recruitment.thankyou</field>
<field name="arch" type="xml">
<t name="Thank you (Recruitment)" t-name="website_hr_recruitment.thankyou">
<t t-if="request.session.get('form_builder_model_model', '') == 'hr.applicant'">
<t t-set="job_sudo" t-value="request.website._website_form_last_record().sudo().job_id"/>
</t>
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure"/>
<div id="jobs_thankyou" class="container">
<div class="row pt24 pb24">
<div class="col pt24 pb24 text-center">
<h1 class="fw-bolder">Congratulations!</h1>
<p class="fw-light pb24">
Your application has been posted successfully,<br class="mb-2"/>
We usually respond within 3 days...
</p>
<img class="img-fluid o_we_custom_image" t-attf-style="width:{{ '50%' if (job_sudo and job_sudo.user_id) else '25%' }} !important;" src="/website_hr_recruitment/static/src/img/job_congratulations.svg" alt="Congratulations!"/>
<div class="row" id="o_recruitment_thank_cta">
<div class="col-lg-12 text-center mt32 mb48">
<p>
<span class="h5 fw-light">In the meantime,</span><br/>
<span class="h3 mt8 mb32 fw-bold">Take a look around our website:</span>
</p>
<a role="button" href="/" class="btn btn-primary btn-lg">Home</a><a role="button" href="/" class="btn btn-primary btn-lg ms-3">About Us</a><a role="button" href="/" class="btn btn-primary btn-lg ms-3">Products</a>
</div>
</div>
</div>
<!-- HR Recruiter Block -->
<t t-if="job_sudo and job_sudo.user_id">
<div class="col-lg-4 align-self-center ps-4 o_cc o_cc2 rounded">
<section class="s_text_block pt24" data-snippet="s_text_block" data-name="Text">
<div class="container">
<p class="text-center mb-0 fs-3 lead">Your <b>contact</b> information is:</p>
</div>
</section>
<section class="s_company_team pt12" data-snippet="s_company_team" data-name="Team">
<div class="container-fluid">
<div class="row justify-content-center">
<div class="o_jobs_hr_recruiter col-10 o_cc o_cc1 mt24 p-4 border border-primary rounded shadow">
<div class="row align-items-center">
<div class="col-lg-5 pb16 o_not_editable">
<img t-att-src="image_data_uri(job_sudo.user_id.avatar_256)" class="img-fluid" loading="lazy"/>
</div>
<div class="col-lg-7 px-1">
<h2 class="o_default_snippet_text text-truncate" t-field="job_sudo.user_id.name"/>
<p class="fw-light text-truncate" t-field="job_sudo.user_id.job_title"/>
</div>
</div>
<div class="row">
<ul class="list-unstyled mb-0">
<li t-if="job_sudo.user_id.work_phone"><i class="fa fa-phone fa-fw me-2"></i><span class="o_force_ltr" t-field="job_sudo.user_id.work_phone"/></li>
<li class="d-inline-flex align-items-baseline"><i class="fa fa-envelope fa-fw me-2"></i><span><a class="text-break" t-attf-href="mailto:#{job_sudo.user_id.email}" t-field="job_sudo.user_id.email"/></span></li>
</ul>
</div>
</div>
</div>
</div>
</section>
<section class="s_text_block pt24" data-snippet="s_text_block" data-name="Text">
<div class="container">
<p class="lead alert px-0">
I usually <strong>answer applications within 3 days</strong>.
<br/><br/>
The next step is either a call or a meeting in our offices.
<br/><br/>
Feel free to <strong>contact me if you want a faster
feedback</strong> or if you don't get news from me
quickly enough.
</p>
</div>
</section>
</div>
</t>
</div>
</div>
<div class="oe_structure"/>
</div>
</t>
</t>
</field>
</record>
<!-- <template id="recruitment_index" name="Jobs">-->
<!-- <t t-call="website.layout">-->