fix: employee, payroll, resignation, recruitment
This commit is contained in:
parent
42e022bb7f
commit
23e8323d6c
|
|
@ -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)])
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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': {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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':
|
||||
|
|
|
|||
|
|
@ -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)])
|
||||
|
|
|
|||
|
|
@ -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">-->
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue