payroll and recruitment multi company bug issues
This commit is contained in:
parent
1696e0ded1
commit
d913750402
|
|
@ -1,6 +1,6 @@
|
||||||
/** @odoo-module **/
|
/** @odoo-module **/
|
||||||
import {standardWidgetProps} from "@web/views/widgets/standard_widget_props";
|
import {standardWidgetProps} from "@web/views/widgets/standard_widget_props";
|
||||||
import {Component, onMounted, useRef, useState, onWillStart, onWillUpdateProps} from "@odoo/owl";
|
import {Component, onMounted, useRef, useState, onWillStart, onWillUpdateProps, useEffect} from "@odoo/owl";
|
||||||
import {registry} from "@web/core/registry";
|
import {registry} from "@web/core/registry";
|
||||||
import {useService} from "@web/core/utils/hooks";
|
import {useService} from "@web/core/utils/hooks";
|
||||||
import {loadJS, loadCSS} from "@web/core/assets";
|
import {loadJS, loadCSS} from "@web/core/assets";
|
||||||
|
|
@ -35,11 +35,22 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.currentState = this.props.record.data.state;
|
||||||
|
|
||||||
onWillUpdateProps(async (nextProps) => {
|
onWillUpdateProps(async (nextProps) => {
|
||||||
if (nextProps.record.data.state !== this.props.record.data.state) {
|
if (nextProps.record.data.state !== this.currentState) {
|
||||||
|
this.currentState = nextProps.record.data.state;
|
||||||
await this.loadGrid();
|
await this.loadGrid();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
this.loadGrid();
|
||||||
|
},
|
||||||
|
() => [this.props.record.data.state]
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadDependencies() {
|
async loadDependencies() {
|
||||||
|
|
@ -498,7 +509,7 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
|
|
||||||
async refreshGrid() {
|
async refreshGrid() {
|
||||||
try {
|
try {
|
||||||
await this.loadGridData();
|
await this.loadGrid();
|
||||||
$(this.gridRef.el).pqGrid("refreshDataAndView");
|
$(this.gridRef.el).pqGrid("refreshDataAndView");
|
||||||
this.notification.add("Data refreshed successfully", {type: 'success'});
|
this.notification.add("Data refreshed successfully", {type: 'success'});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
from . import models
|
from . import models
|
||||||
|
from . import wizard
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,16 @@
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'views/hr_payslip_employees_views.xml',
|
'views/hr_payslip_employees_views.xml',
|
||||||
'views/hr_salary_advance_views.xml',
|
'views/hr_salary_advance_views.xml',
|
||||||
|
'views/hr_payroll_edit_payslip_lines_wizard_views.xml',
|
||||||
'views/menus.xml',
|
'views/menus.xml',
|
||||||
'views/hr_work_entry_views.xml',
|
'views/hr_work_entry_views.xml',
|
||||||
|
'views/hr_payslip_refund_views.xml',
|
||||||
|
'wizard/hr_payslip_refund_wizard_views.xml',
|
||||||
],
|
],
|
||||||
'assets': {
|
'assets': {
|
||||||
'web.assets_backend': [
|
'web.assets_backend': [
|
||||||
'hr_payroll_extended/static/src/js/consolidated_payslip_grid_patch.js',
|
'hr_payroll_extended/static/src/js/consolidated_payslip_grid_patch.js',
|
||||||
|
'hr_payroll_extended/static/src/js/payslip_line_one2many_patch.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
from . import hr_work_entry
|
from . import hr_work_entry
|
||||||
from . import hr_salary_advance
|
from . import hr_salary_advance
|
||||||
from . import hr_payslip_employees
|
from . import hr_payslip_employees
|
||||||
|
from . import hr_payslip
|
||||||
|
from . import hr_payslip_run
|
||||||
|
from . import hr_leave
|
||||||
|
from . import hr_payslip_refund
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
|
class HrLeave(models.Model):
|
||||||
|
_inherit = "hr.leave"
|
||||||
|
|
||||||
|
def _refresh_payroll_extended_payslips(self):
|
||||||
|
payslips = self.env["hr.payslip"].sudo().search([
|
||||||
|
("employee_id", "in", self.mapped("employee_id").ids),
|
||||||
|
("state", "in", ("draft", "verify")),
|
||||||
|
])
|
||||||
|
for leave in self:
|
||||||
|
impacted = payslips.filtered(
|
||||||
|
lambda slip: slip.employee_id == leave.employee_id
|
||||||
|
and leave.date_from
|
||||||
|
and leave.date_to
|
||||||
|
and slip.date_from <= leave.date_to.date()
|
||||||
|
and slip.date_to >= leave.date_from.date()
|
||||||
|
)
|
||||||
|
for payslip in impacted:
|
||||||
|
payslip.mapped("worked_days_line_ids").unlink()
|
||||||
|
payslip.mapped("line_ids").unlink()
|
||||||
|
payslip.write({"edited": False})
|
||||||
|
payslip._compute_worked_days_line_ids()
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
def action_validate(self, check_state=True):
|
||||||
|
res = super().action_validate(check_state=check_state)
|
||||||
|
self._refresh_payroll_extended_payslips()
|
||||||
|
return res
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
from odoo.tools.misc import format_date
|
||||||
|
|
||||||
|
|
||||||
|
class HrPayslip(models.Model):
|
||||||
|
_inherit = "hr.payslip"
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
payslips = super().create(vals_list)
|
||||||
|
payslips._check_duplicate_payslip_periods()
|
||||||
|
return payslips
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
res = super().write(vals)
|
||||||
|
if {"employee_id", "date_from", "date_to", "state"} & vals.keys():
|
||||||
|
self._check_duplicate_payslip_periods()
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
from odoo import api, fields, models
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
class HrPayslipEmployees(models.TransientModel):
|
class HrPayslipEmployees(models.TransientModel):
|
||||||
|
|
@ -83,3 +84,25 @@ class HrPayslipEmployees(models.TransientModel):
|
||||||
"view_mode": "form",
|
"view_mode": "form",
|
||||||
"target": "new",
|
"target": "new",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def compute_sheet(self):
|
||||||
|
self.ensure_one()
|
||||||
|
date_start, date_end = self._get_generation_dates()
|
||||||
|
if date_start and date_end:
|
||||||
|
employees = self.with_context(active_test=False).employee_ids
|
||||||
|
payslip_run = self.env["hr.payslip.run"].browse(self.env.context.get("active_id")).exists()
|
||||||
|
if payslip_run:
|
||||||
|
employees -= payslip_run.slip_ids.employee_id
|
||||||
|
duplicate_payslips = self.env["hr.payslip"].search([
|
||||||
|
("employee_id", "in", employees.ids),
|
||||||
|
("state", "not in", ("draft", "cancel")),
|
||||||
|
("date_from", "<=", date_end),
|
||||||
|
("date_to", ">=", date_start),
|
||||||
|
])
|
||||||
|
if duplicate_payslips:
|
||||||
|
employees_with_duplicates = ", ".join(sorted(set(duplicate_payslips.mapped("employee_id.name"))))
|
||||||
|
raise UserError(_(
|
||||||
|
"Payslips already exist for the selected period for: %s. Only draft or canceled payslips can be duplicated.",
|
||||||
|
employees_with_duplicates,
|
||||||
|
))
|
||||||
|
return super().compute_sheet()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
from odoo.tools.misc import format_date
|
||||||
|
|
||||||
|
|
||||||
|
class HrPayslip(models.Model):
|
||||||
|
_inherit = "hr.payslip"
|
||||||
|
|
||||||
|
state = fields.Selection(
|
||||||
|
selection_add=[
|
||||||
|
("partial_refund", "Partial Refund"),
|
||||||
|
("full_refund", "Full Refund"),
|
||||||
|
],
|
||||||
|
ondelete={
|
||||||
|
"partial_refund": "set default",
|
||||||
|
"full_refund": "set default",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
original_refund_payslip_id = fields.Many2one(
|
||||||
|
"hr.payslip",
|
||||||
|
string="Original Payslip",
|
||||||
|
copy=False,
|
||||||
|
index=True,
|
||||||
|
help="Payslip that this refund belongs to.",
|
||||||
|
)
|
||||||
|
refund_payslip_ids = fields.One2many(
|
||||||
|
"hr.payslip",
|
||||||
|
"original_refund_payslip_id",
|
||||||
|
string="Refund Payslips",
|
||||||
|
copy=False,
|
||||||
|
)
|
||||||
|
refund_payslip_count = fields.Integer(compute="_compute_refund_details")
|
||||||
|
partial_refund_amount = fields.Monetary(
|
||||||
|
string="Refund Amount",
|
||||||
|
currency_field="currency_id",
|
||||||
|
copy=False,
|
||||||
|
help="Amount returned by the employee for this partial refund adjustment.",
|
||||||
|
)
|
||||||
|
refund_history_payslip_ids = fields.Many2many(
|
||||||
|
"hr.payslip",
|
||||||
|
compute="_compute_refund_history_payslip_ids",
|
||||||
|
string="Refund History",
|
||||||
|
)
|
||||||
|
refunded_amount = fields.Monetary(
|
||||||
|
string="Refunded Amount",
|
||||||
|
compute="_compute_refund_details",
|
||||||
|
currency_field="currency_id",
|
||||||
|
help="Total amount refunded against this payslip.",
|
||||||
|
)
|
||||||
|
refundable_amount = fields.Monetary(
|
||||||
|
string="Refundable Amount",
|
||||||
|
compute="_compute_refund_details",
|
||||||
|
currency_field="currency_id",
|
||||||
|
help="Remaining amount available for refund.",
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends(
|
||||||
|
"net_wage",
|
||||||
|
"state",
|
||||||
|
"partial_refund_amount",
|
||||||
|
"refund_payslip_ids.state",
|
||||||
|
"refund_payslip_ids.partial_refund_amount",
|
||||||
|
)
|
||||||
|
def _compute_refund_details(self):
|
||||||
|
for payslip in self:
|
||||||
|
reference = payslip.original_refund_payslip_id or payslip
|
||||||
|
refund_slips = reference.refund_payslip_ids.filtered(lambda slip: slip.state != "cancel")
|
||||||
|
refunded_amount = sum(refund_slips.mapped("partial_refund_amount"))
|
||||||
|
total_amount = abs(payslip.net_wage)
|
||||||
|
if reference.state == "full_refund":
|
||||||
|
refunded_amount = max(refunded_amount, abs(reference.net_wage))
|
||||||
|
if payslip.original_refund_payslip_id:
|
||||||
|
total_amount = abs(reference.net_wage)
|
||||||
|
payslip.refund_payslip_count = len(refund_slips) if payslip == reference else 0
|
||||||
|
payslip.refunded_amount = refunded_amount
|
||||||
|
payslip.refundable_amount = max(total_amount - refunded_amount, 0.0)
|
||||||
|
|
||||||
|
@api.depends("employee_id", "date_from", "date_to", "original_refund_payslip_id")
|
||||||
|
def _compute_refund_history_payslip_ids(self):
|
||||||
|
for payslip in self:
|
||||||
|
reference = payslip.original_refund_payslip_id or payslip
|
||||||
|
if not reference.employee_id or not reference.date_from or not reference.date_to:
|
||||||
|
payslip.refund_history_payslip_ids = False
|
||||||
|
continue
|
||||||
|
payslip.refund_history_payslip_ids = self.search([
|
||||||
|
("id", "!=", payslip.id),
|
||||||
|
("employee_id", "=", reference.employee_id.id),
|
||||||
|
("state", "!=", "cancel"),
|
||||||
|
("date_from", "<=", reference.date_to),
|
||||||
|
("date_to", ">=", reference.date_from),
|
||||||
|
"|",
|
||||||
|
("credit_note", "=", True),
|
||||||
|
("original_refund_payslip_id", "!=", False),
|
||||||
|
])
|
||||||
|
|
||||||
|
def _check_duplicate_payslip_periods(self):
|
||||||
|
protected_slips = self.filtered(
|
||||||
|
lambda slip: (
|
||||||
|
slip.employee_id
|
||||||
|
and slip.date_from
|
||||||
|
and slip.date_to
|
||||||
|
and slip.state not in ("draft", "cancel", "full_refund", "partial_refund")
|
||||||
|
and not slip.credit_note
|
||||||
|
and not slip.original_refund_payslip_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for slip in protected_slips:
|
||||||
|
duplicate = self.search([
|
||||||
|
("id", "!=", slip.id),
|
||||||
|
("employee_id", "=", slip.employee_id.id),
|
||||||
|
("state", "not in", ("draft", "cancel", "full_refund", "partial_refund")),
|
||||||
|
("credit_note", "=", False),
|
||||||
|
("date_from", "<=", slip.date_to),
|
||||||
|
("date_to", ">=", slip.date_from),
|
||||||
|
], limit=1)
|
||||||
|
if duplicate:
|
||||||
|
raise ValidationError(_(
|
||||||
|
"A non-draft payslip already exists for %(employee)s in the selected period (%(date_from)s - %(date_to)s). "
|
||||||
|
"Cancel or reset the duplicate payslip to draft before confirming another one.",
|
||||||
|
employee=slip.employee_id.name,
|
||||||
|
date_from=format_date(self.env, slip.date_from),
|
||||||
|
date_to=format_date(self.env, slip.date_to),
|
||||||
|
))
|
||||||
|
|
||||||
|
def refund_sheet(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.credit_note or self.original_refund_payslip_id:
|
||||||
|
raise ValidationError(_("Refunds can only be created from the original payslip."))
|
||||||
|
if self.state not in ("done", "paid", "partial_refund"):
|
||||||
|
raise ValidationError(_("Only done, paid, or partially refunded payslips can be refunded."))
|
||||||
|
return {
|
||||||
|
"name": _("Refund Payslip"),
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"res_model": "hr.payslip.refund.wizard",
|
||||||
|
"view_mode": "form",
|
||||||
|
"target": "new",
|
||||||
|
"context": {
|
||||||
|
"default_payslip_id": self.id,
|
||||||
|
"default_refund_amount": self.refundable_amount or abs(self.net_wage),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _is_invalid(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.state in ("partial_refund", "full_refund"):
|
||||||
|
return False
|
||||||
|
return super()._is_invalid()
|
||||||
|
|
||||||
|
def action_view_refund_payslips(self):
|
||||||
|
self.ensure_one()
|
||||||
|
return {
|
||||||
|
"name": _("Refund Payslips"),
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"res_model": "hr.payslip",
|
||||||
|
"view_mode": "list,form",
|
||||||
|
"domain": [("id", "in", self.refund_payslip_ids.ids)],
|
||||||
|
"context": {"default_original_refund_payslip_id": self.id},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
|
from odoo import api, models
|
||||||
|
|
||||||
|
|
||||||
|
class HrPayslipRun(models.Model):
|
||||||
|
_inherit = "hr.payslip.run"
|
||||||
|
|
||||||
|
@api.onchange("date_start")
|
||||||
|
def _onchange_date_start_extended(self):
|
||||||
|
for payslip_run in self:
|
||||||
|
if not payslip_run.date_start:
|
||||||
|
continue
|
||||||
|
if payslip_run.date_start.day == 1:
|
||||||
|
payslip_run.date_end = payslip_run.date_start + relativedelta(day=31)
|
||||||
|
else:
|
||||||
|
payslip_run.date_end = payslip_run.date_start + relativedelta(months=1)
|
||||||
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
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_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_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
|
||||||
|
|
|
||||||
|
|
|
@ -77,6 +77,37 @@ patch(ConsolidatedPayslipGrid.prototype, {
|
||||||
this.showNotification("Data refreshed successfully");
|
this.showNotification("Data refreshed successfully");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getColumns() {
|
||||||
|
const columns = await super.getColumns(...arguments);
|
||||||
|
const component = this;
|
||||||
|
const editColumn = columns.find((column) => column.title === "Edit");
|
||||||
|
if (editColumn) {
|
||||||
|
editColumn.postRender = function (ui) {
|
||||||
|
const grid = this;
|
||||||
|
const $cell = grid.getCell(ui);
|
||||||
|
$cell.find(".row-btn-edit")
|
||||||
|
.button({ icons: { primary: "ui-icon-pencil" } })
|
||||||
|
.off("click.hr_payroll_extended")
|
||||||
|
.on("click.hr_payroll_extended", async function (evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopImmediatePropagation();
|
||||||
|
const action = await component.orm.call(
|
||||||
|
"hr.payslip",
|
||||||
|
"action_edit_payslip_lines",
|
||||||
|
[ui.rowData.id]
|
||||||
|
);
|
||||||
|
action.views = [[false, "form"]];
|
||||||
|
await component.action.doAction(action, {
|
||||||
|
onClose: async () => {
|
||||||
|
await component.refreshGrid();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return columns;
|
||||||
|
},
|
||||||
|
|
||||||
async saveChanges() {
|
async saveChanges() {
|
||||||
const grid = this._getGridInstance();
|
const grid = this._getGridInstance();
|
||||||
const updatedData = grid.option("dataModel.data");
|
const updatedData = grid.option("dataModel.data");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { WorkedDaysField } from "@hr_payroll/js/payslip_line_one2many";
|
||||||
|
|
||||||
|
const fieldGetter = Object.getOwnPropertyDescriptor(
|
||||||
|
Object.getPrototypeOf(WorkedDaysField.prototype),
|
||||||
|
"fieldComponentProps"
|
||||||
|
).get;
|
||||||
|
|
||||||
|
Object.defineProperty(WorkedDaysField.prototype, "fieldComponentProps", {
|
||||||
|
get() {
|
||||||
|
const props = fieldGetter.call(this);
|
||||||
|
const record = this.props.record;
|
||||||
|
if (!record.isExtendedWorkedDaysField) {
|
||||||
|
record.isExtendedWorkedDaysField = true;
|
||||||
|
const oldUpdate = record.update.bind(record);
|
||||||
|
record.update = async (changes) => {
|
||||||
|
if ("amount" in changes || "quantity" in changes || "number_of_days" in changes || "number_of_hours" in changes) {
|
||||||
|
await oldUpdate(changes);
|
||||||
|
await record._parentRecord.save();
|
||||||
|
const wizardId = record.model.config.resId;
|
||||||
|
if (wizardId) {
|
||||||
|
const action = await this.orm.call(
|
||||||
|
"hr.payroll.edit.payslip.lines.wizard",
|
||||||
|
"recompute_worked_days_lines",
|
||||||
|
[wizardId]
|
||||||
|
);
|
||||||
|
if (action) {
|
||||||
|
await this.actionService.doAction(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await oldUpdate(changes);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return props;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="hr_payroll_edit_payslip_lines_form_wizard_inherit_extended" model="ir.ui.view">
|
||||||
|
<field name="name">hr.payroll.edit.payslip.lines.wizard.form.inherit.extended</field>
|
||||||
|
<field name="model">hr.payroll.edit.payslip.lines.wizard</field>
|
||||||
|
<field name="inherit_id" ref="hr_payroll.hr_payroll_edit_payslip_lines_form_wizard"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='worked_days_line_ids']" position="replace">
|
||||||
|
<notebook>
|
||||||
|
<page string="Worked Days" name="worked_days">
|
||||||
|
<field name="worked_days_line_ids" widget="worked_days_line_one2many" nolabel="1" context="{'default_slip_id': payslip_id}">
|
||||||
|
<list string="Worked Days" editable="bottom" delete="0">
|
||||||
|
<field name="work_entry_type_id"/>
|
||||||
|
<field name="name" required="1"/>
|
||||||
|
<field name="number_of_days"/>
|
||||||
|
<field name="number_of_hours"/>
|
||||||
|
<field name="code" optional="hide"/>
|
||||||
|
<field name="amount"/>
|
||||||
|
<field name="ytd" optional="show" readonly="0" column_invisible="not parent.ytd_computation"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Leaves" name="leaves">
|
||||||
|
<button name="action_open_leave_application" string="Apply Leave" type="object" class="oe_highlight"/>
|
||||||
|
<field name="period_leave_ids" nolabel="1" readonly="1">
|
||||||
|
<list string="Leaves in Payslip Period">
|
||||||
|
<field name="holiday_status_id"/>
|
||||||
|
<field name="request_date_from"/>
|
||||||
|
<field name="request_date_to"/>
|
||||||
|
<field name="number_of_days"/>
|
||||||
|
<field name="state"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Salary Structure" name="salary_structure">
|
||||||
|
<field name="line_ids" widget="payslip_line_one2many" nolabel="1" context="{'default_slip_id': payslip_id}">
|
||||||
|
<list string="Salary Structure" editable="bottom" decoration-info="total == 0" delete="0">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="slip_id" column_invisible="True"/>
|
||||||
|
<field name="struct_id" column_invisible="True"/>
|
||||||
|
<field name="name" required="True"/>
|
||||||
|
<field name="code" readonly="1" force_save="1" optional="hide"/>
|
||||||
|
<field name="category_id" readonly="1" column_invisible="True" force_save="1"/>
|
||||||
|
<field name="quantity"/>
|
||||||
|
<field name="rate" readonly="1" force_save="1"/>
|
||||||
|
<field name="salary_rule_id" options="{'no_create_edit': True}" required="1"/>
|
||||||
|
<field name="amount"/>
|
||||||
|
<field name="total" readonly="1" force_save="1"/>
|
||||||
|
<field name="ytd" optional="show" readonly="0" force_save="1" column_invisible="not parent.ytd_computation"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='line_ids']" position="replace"/>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_hr_payslip_tree_refund_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr.payslip.list.refund.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='state']" position="attributes">
|
||||||
|
<attribute name="decoration-muted">state == 'partial_refund'</attribute>
|
||||||
|
<attribute name="decoration-danger">state == 'full_refund'</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='net_wage']" position="after">
|
||||||
|
<field name="partial_refund_amount" widget="monetary" options="{'currency_field': 'currency_id'}" optional="hide"/>
|
||||||
|
<field name="refunded_amount" widget="monetary" options="{'currency_field': 'currency_id'}" optional="hide"/>
|
||||||
|
<field name="refundable_amount" widget="monetary" options="{'currency_field': 'currency_id'}" optional="hide"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_hr_payslip_form_refund_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr.payslip.form.refund.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='refund_sheet']" position="attributes">
|
||||||
|
<attribute name="invisible">1</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//button[@name='action_payslip_payment_report']" position="after">
|
||||||
|
<button string="Refund" name="refund_sheet" type="object" class="oe_highlight"
|
||||||
|
invisible="credit_note or original_refund_payslip_id or state not in ('paid', 'partial_refund')"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='state']" position="attributes">
|
||||||
|
<attribute name="statusbar_visible">draft,verify,done,paid,partial_refund,full_refund</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//div[@name='button_box']" position="inside">
|
||||||
|
<button type="object" class="oe_stat_button" name="action_view_refund_payslips"
|
||||||
|
icon="fa-undo" invisible="refund_payslip_count == 0">
|
||||||
|
<field name="refund_payslip_count" widget="statinfo" string="Refunds"/>
|
||||||
|
</button>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='has_refund_slip']" position="after">
|
||||||
|
<field name="original_refund_payslip_id" invisible="not original_refund_payslip_id" readonly="1"/>
|
||||||
|
<field name="partial_refund_amount" readonly="1" invisible="not partial_refund_amount"/>
|
||||||
|
<field name="refund_payslip_count" invisible="1"/>
|
||||||
|
<field name="refunded_amount" readonly="1" invisible="not refunded_amount"/>
|
||||||
|
<field name="refundable_amount" readonly="1" invisible="not refunded_amount"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//page[@name='account_info']" position="before">
|
||||||
|
<page string="Refund Details" name="refund_details"
|
||||||
|
invisible="not refund_payslip_ids and not original_refund_payslip_id and not refund_history_payslip_ids and not refunded_amount">
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="original_refund_payslip_id" readonly="1"/>
|
||||||
|
<field name="refunded_amount" readonly="1"/>
|
||||||
|
<field name="refundable_amount" readonly="1"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<separator string="Refund Payslips" invisible="not refund_payslip_ids"/>
|
||||||
|
<field name="refund_payslip_ids" readonly="1" invisible="not refund_payslip_ids">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="date_from"/>
|
||||||
|
<field name="date_to"/>
|
||||||
|
<field name="partial_refund_amount"/>
|
||||||
|
<field name="net_wage"/>
|
||||||
|
<field name="state"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
<separator string="Refund History For This Period" invisible="not refund_history_payslip_ids"/>
|
||||||
|
<field name="refund_history_payslip_ids" readonly="1" invisible="not refund_history_payslip_ids">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="date_from"/>
|
||||||
|
<field name="date_to"/>
|
||||||
|
<field name="partial_refund_amount"/>
|
||||||
|
<field name="net_wage"/>
|
||||||
|
<field name="state"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
from . import hr_payroll_edit_payslip_lines_wizard
|
||||||
|
from . import hr_payslip_refund_wizard
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
from odoo import Command, api, fields, models, _
|
||||||
|
|
||||||
|
|
||||||
|
class HrPayrollEditPayslipLinesWizard(models.TransientModel):
|
||||||
|
_inherit = "hr.payroll.edit.payslip.lines.wizard"
|
||||||
|
|
||||||
|
period_leave_ids = fields.Many2many("hr.leave", compute="_compute_period_leave_ids", string="Leaves in Period")
|
||||||
|
|
||||||
|
def _reload_wizard_action(self):
|
||||||
|
self.ensure_one()
|
||||||
|
return {
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"res_model": self._name,
|
||||||
|
"view_mode": "form",
|
||||||
|
"res_id": self.id,
|
||||||
|
"views": [(False, "form")],
|
||||||
|
"target": "new",
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.depends("payslip_id.employee_id", "payslip_id.date_from", "payslip_id.date_to")
|
||||||
|
def _compute_period_leave_ids(self):
|
||||||
|
for wizard in self:
|
||||||
|
if not wizard.payslip_id.employee_id or not wizard.payslip_id.date_from or not wizard.payslip_id.date_to:
|
||||||
|
wizard.period_leave_ids = False
|
||||||
|
continue
|
||||||
|
date_from = fields.Datetime.to_datetime(wizard.payslip_id.date_from)
|
||||||
|
date_to = fields.Datetime.to_datetime(wizard.payslip_id.date_to).replace(hour=23, minute=59, second=59)
|
||||||
|
wizard.period_leave_ids = self.env["hr.leave"].sudo().search([
|
||||||
|
("employee_id", "=", wizard.payslip_id.employee_id.id),
|
||||||
|
("state", "in", ("confirm", "validate1", "validate")),
|
||||||
|
("date_from", "<=", date_to),
|
||||||
|
("date_to", ">=", date_from),
|
||||||
|
])
|
||||||
|
|
||||||
|
def action_open_leave_application(self):
|
||||||
|
self.ensure_one()
|
||||||
|
return {
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"name": _("Apply Leave"),
|
||||||
|
"res_model": "hr.leave",
|
||||||
|
"view_mode": "form",
|
||||||
|
"target": "new",
|
||||||
|
"context": {
|
||||||
|
"default_employee_id": self.payslip_id.employee_id.id,
|
||||||
|
"default_request_date_from": self.payslip_id.date_from,
|
||||||
|
"default_request_date_to": self.payslip_id.date_to,
|
||||||
|
"default_private_name": _("Added from payslip %s", self.payslip_id.display_name),
|
||||||
|
"hr_payroll_extended_refresh_payslip_id": self.payslip_id.id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _compute_worked_days_amounts(self):
|
||||||
|
self.ensure_one()
|
||||||
|
payslip = self.payslip_id
|
||||||
|
hours_per_day = payslip._get_worked_day_lines_hours_per_day()
|
||||||
|
total_hours = sum(self.worked_days_line_ids.mapped("number_of_hours"))
|
||||||
|
days_count = float((payslip.date_to - payslip.date_from).days + 1)
|
||||||
|
unpaid_type_ids = set(payslip.struct_id.unpaid_work_entry_type_ids.ids)
|
||||||
|
|
||||||
|
for line in self.worked_days_line_ids:
|
||||||
|
if hours_per_day and line.number_of_days and not line.number_of_hours:
|
||||||
|
line.number_of_hours = line.number_of_days * hours_per_day
|
||||||
|
elif hours_per_day and line.number_of_days:
|
||||||
|
line.number_of_hours = line.number_of_days * hours_per_day
|
||||||
|
|
||||||
|
is_paid = line.work_entry_type_id.id not in unpaid_type_ids
|
||||||
|
if not payslip.contract_id or line.code == "OUT":
|
||||||
|
line.amount = 0
|
||||||
|
elif payslip.wage_type == "hourly":
|
||||||
|
line.amount = payslip.contract_id.hourly_wage * line.number_of_hours if is_paid else 0
|
||||||
|
elif line.work_entry_type_id.is_leave:
|
||||||
|
daily_wage = payslip.contract_id.contract_wage / days_count if days_count else 0
|
||||||
|
days = round(line.number_of_hours / hours_per_day, 5) if hours_per_day else line.number_of_days
|
||||||
|
line.amount = daily_wage * days if is_paid else -(daily_wage * days)
|
||||||
|
else:
|
||||||
|
days_to_remove = round((total_hours - line.number_of_hours) / hours_per_day, 5) if hours_per_day else 0
|
||||||
|
daily_wage = payslip.contract_id.contract_wage / days_count if days_count else 0
|
||||||
|
line.amount = payslip.contract_id.contract_wage - (daily_wage * (days_to_remove if days_to_remove > 0 else 1)) if is_paid else 0
|
||||||
|
|
||||||
|
def _recompute_salary_lines_from_worked_days(self):
|
||||||
|
self.ensure_one()
|
||||||
|
localdict = self.payslip_id._get_localdict()
|
||||||
|
localdict["worked_days"] = {line.code: line for line in self.worked_days_line_ids if line.code}
|
||||||
|
payslip = self.payslip_id.with_context(force_payslip_localdict=localdict)
|
||||||
|
self.line_ids = [Command.clear()] + [Command.create(line) for line in payslip._get_payslip_lines()]
|
||||||
|
|
||||||
|
def recompute_worked_days_lines(self):
|
||||||
|
self.ensure_one()
|
||||||
|
self._compute_worked_days_amounts()
|
||||||
|
self._recompute_salary_lines_from_worked_days()
|
||||||
|
return self._reload_wizard_action()
|
||||||
|
|
||||||
|
|
||||||
|
class HrPayrollEditPayslipWorkedDaysLine(models.TransientModel):
|
||||||
|
_inherit = "hr.payroll.edit.payslip.worked.days.line"
|
||||||
|
|
||||||
|
@api.onchange("number_of_days")
|
||||||
|
def _onchange_number_of_days_extended(self):
|
||||||
|
for line in self:
|
||||||
|
payslip = line.edit_payslip_lines_wizard_id.payslip_id
|
||||||
|
if payslip and line.number_of_days:
|
||||||
|
line.number_of_hours = line.number_of_days * payslip._get_worked_day_lines_hours_per_day()
|
||||||
|
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
from odoo.tools.float_utils import float_compare, float_is_zero
|
||||||
|
|
||||||
|
|
||||||
|
class HrPayslipRefundWizard(models.TransientModel):
|
||||||
|
_name = "hr.payslip.refund.wizard"
|
||||||
|
_description = "Payslip Refund"
|
||||||
|
|
||||||
|
payslip_id = fields.Many2one("hr.payslip", string="Payslip", required=True, readonly=True)
|
||||||
|
refund_type = fields.Selection(
|
||||||
|
[
|
||||||
|
("full", "Full Refund"),
|
||||||
|
("partial", "Partial Refund"),
|
||||||
|
],
|
||||||
|
string="Refund Type",
|
||||||
|
required=True,
|
||||||
|
default="full",
|
||||||
|
)
|
||||||
|
refund_amount = fields.Monetary(
|
||||||
|
string="Refund Amount",
|
||||||
|
required=True,
|
||||||
|
currency_field="currency_id",
|
||||||
|
)
|
||||||
|
currency_id = fields.Many2one(related="payslip_id.currency_id")
|
||||||
|
original_amount = fields.Monetary(
|
||||||
|
string="Original Net Amount",
|
||||||
|
related="payslip_id.net_wage",
|
||||||
|
currency_field="currency_id",
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
already_refunded_amount = fields.Monetary(
|
||||||
|
string="Already Refunded",
|
||||||
|
related="payslip_id.refunded_amount",
|
||||||
|
currency_field="currency_id",
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
refundable_amount = fields.Monetary(
|
||||||
|
string="Available to Refund",
|
||||||
|
related="payslip_id.refundable_amount",
|
||||||
|
currency_field="currency_id",
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
previous_refund_ids = fields.Many2many(
|
||||||
|
"hr.payslip",
|
||||||
|
compute="_compute_previous_refund_ids",
|
||||||
|
string="Previous Refunds",
|
||||||
|
)
|
||||||
|
reason = fields.Text(string="Reason")
|
||||||
|
|
||||||
|
@api.depends("payslip_id", "payslip_id.date_from", "payslip_id.date_to", "payslip_id.employee_id")
|
||||||
|
def _compute_previous_refund_ids(self):
|
||||||
|
for wizard in self:
|
||||||
|
if not wizard.payslip_id:
|
||||||
|
wizard.previous_refund_ids = False
|
||||||
|
continue
|
||||||
|
linked_refunds = self.env["hr.payslip"].search([
|
||||||
|
("id", "!=", wizard.payslip_id.id),
|
||||||
|
("employee_id", "=", wizard.payslip_id.employee_id.id),
|
||||||
|
("state", "!=", "cancel"),
|
||||||
|
("original_refund_payslip_id", "=", wizard.payslip_id.id),
|
||||||
|
])
|
||||||
|
legacy_refunds = self.env["hr.payslip"].search([
|
||||||
|
("id", "!=", wizard.payslip_id.id),
|
||||||
|
("employee_id", "=", wizard.payslip_id.employee_id.id),
|
||||||
|
("state", "!=", "cancel"),
|
||||||
|
("credit_note", "=", True),
|
||||||
|
("date_from", "<=", wizard.payslip_id.date_to),
|
||||||
|
("date_to", ">=", wizard.payslip_id.date_from),
|
||||||
|
])
|
||||||
|
wizard.previous_refund_ids = linked_refunds | legacy_refunds
|
||||||
|
|
||||||
|
@api.onchange("refund_type", "payslip_id")
|
||||||
|
def _onchange_refund_type(self):
|
||||||
|
for wizard in self:
|
||||||
|
if not wizard.payslip_id:
|
||||||
|
continue
|
||||||
|
if wizard.refund_type == "full":
|
||||||
|
wizard.refund_amount = wizard.payslip_id.refundable_amount or abs(wizard.payslip_id.net_wage)
|
||||||
|
elif not wizard.refund_amount:
|
||||||
|
wizard.refund_amount = wizard.payslip_id.refundable_amount
|
||||||
|
|
||||||
|
def _validate_refund(self):
|
||||||
|
self.ensure_one()
|
||||||
|
payslip = self.payslip_id
|
||||||
|
if payslip.credit_note or payslip.original_refund_payslip_id:
|
||||||
|
raise ValidationError(_("Refunds can only be created from the original payslip."))
|
||||||
|
if payslip.state not in ("done", "paid", "partial_refund"):
|
||||||
|
raise ValidationError(_("Only done, paid, or partially refunded payslips can be refunded."))
|
||||||
|
if self.refund_amount <= 0:
|
||||||
|
raise ValidationError(_("Refund amount must be greater than zero."))
|
||||||
|
|
||||||
|
precision = payslip.currency_id.rounding if payslip.currency_id else 0.01
|
||||||
|
if self.refund_type == "full" and not float_is_zero(
|
||||||
|
self.refund_amount - payslip.refundable_amount,
|
||||||
|
precision_rounding=precision,
|
||||||
|
):
|
||||||
|
raise ValidationError(_("A full refund must refund the complete remaining amount."))
|
||||||
|
if float_compare(self.refund_amount, payslip.refundable_amount, precision_rounding=precision) > 0:
|
||||||
|
raise ValidationError(_("Refund amount cannot be greater than the available refundable amount."))
|
||||||
|
|
||||||
|
def _scale_adjusted_payslip_lines(self, adjusted_payslip, ratio):
|
||||||
|
adjusted_payslip.ensure_one()
|
||||||
|
for worked_day in adjusted_payslip.worked_days_line_ids:
|
||||||
|
worked_day.write({
|
||||||
|
"amount": worked_day.amount * ratio,
|
||||||
|
})
|
||||||
|
for line in adjusted_payslip.input_line_ids:
|
||||||
|
line.write({"amount": line.amount * ratio})
|
||||||
|
for line in adjusted_payslip.line_ids:
|
||||||
|
line.write({
|
||||||
|
"amount": line.amount * ratio,
|
||||||
|
"total": line.total * ratio,
|
||||||
|
})
|
||||||
|
|
||||||
|
def action_confirm_refund(self):
|
||||||
|
self.ensure_one()
|
||||||
|
self._validate_refund()
|
||||||
|
|
||||||
|
payslip = self.payslip_id
|
||||||
|
if self.refund_type == "full":
|
||||||
|
note = self.reason or _("Full refund created.")
|
||||||
|
payslip.refund_payslip_ids.filtered(lambda slip: slip.state == "paid").write({"state": "partial_refund"})
|
||||||
|
payslip.write({
|
||||||
|
"state": "full_refund",
|
||||||
|
"note": "%s\n%s" % (payslip.note or "", note),
|
||||||
|
})
|
||||||
|
return {"type": "ir.actions.act_window_close"}
|
||||||
|
|
||||||
|
original_net = abs(payslip.net_wage)
|
||||||
|
if not original_net:
|
||||||
|
raise ValidationError(_("Cannot create a partial refund for a payslip with zero net amount."))
|
||||||
|
|
||||||
|
adjusted_net = payslip.refundable_amount - self.refund_amount
|
||||||
|
precision = payslip.currency_id.rounding if payslip.currency_id else 0.01
|
||||||
|
if float_compare(adjusted_net, 0.0, precision_rounding=precision) <= 0:
|
||||||
|
raise ValidationError(_("Use Full Refund when no amount should remain on the payslip."))
|
||||||
|
ratio = adjusted_net / original_net
|
||||||
|
payslip.refund_payslip_ids.filtered(lambda slip: slip.state == "paid").write({"state": "partial_refund"})
|
||||||
|
adjusted_payslip = payslip.copy({
|
||||||
|
"credit_note": False,
|
||||||
|
"original_refund_payslip_id": payslip.id,
|
||||||
|
"partial_refund_amount": self.refund_amount,
|
||||||
|
"name": _("Adjusted Payslip: %(payslip)s", payslip=payslip.name),
|
||||||
|
"edited": True,
|
||||||
|
"state": "paid",
|
||||||
|
"paid_date": fields.Date.today(),
|
||||||
|
"note": self.reason or _("Partial refund of %(amount)s for %(payslip)s", amount=self.refund_amount, payslip=payslip.name),
|
||||||
|
})
|
||||||
|
self._scale_adjusted_payslip_lines(adjusted_payslip, ratio)
|
||||||
|
payslip.write({"state": "partial_refund"})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"name": _("Adjusted Payslip"),
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"res_model": "hr.payslip",
|
||||||
|
"view_mode": "form",
|
||||||
|
"res_id": adjusted_payslip.id,
|
||||||
|
"target": "current",
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_hr_payslip_refund_wizard_form" model="ir.ui.view">
|
||||||
|
<field name="name">hr.payslip.refund.wizard.form</field>
|
||||||
|
<field name="model">hr.payslip.refund.wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Refund Payslip">
|
||||||
|
<group>
|
||||||
|
<field name="payslip_id"/>
|
||||||
|
<field name="refund_type" widget="radio"/>
|
||||||
|
<field name="currency_id" invisible="1"/>
|
||||||
|
<field name="original_amount"/>
|
||||||
|
<field name="already_refunded_amount"/>
|
||||||
|
<field name="refundable_amount"/>
|
||||||
|
<field name="refund_amount" readonly="refund_type == 'full'"/>
|
||||||
|
<field name="reason" placeholder="Reason for refund"/>
|
||||||
|
</group>
|
||||||
|
<group string="Previous Refunds" invisible="not previous_refund_ids">
|
||||||
|
<field name="previous_refund_ids" nolabel="1" readonly="1">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="date_from"/>
|
||||||
|
<field name="date_to"/>
|
||||||
|
<field name="net_wage"/>
|
||||||
|
<field name="state"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</group>
|
||||||
|
<footer>
|
||||||
|
<button string="Confirm Refund" name="action_confirm_refund" type="object" class="btn-primary"/>
|
||||||
|
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
@ -583,6 +583,7 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
|
||||||
"notice_period": parsed_data.get("notice_period"),
|
"notice_period": parsed_data.get("notice_period"),
|
||||||
"applicant_comments": parsed_data.get("summary"),
|
"applicant_comments": parsed_data.get("summary"),
|
||||||
"hr_job_recruitment": self.job_recruitment_id.id,
|
"hr_job_recruitment": self.job_recruitment_id.id,
|
||||||
|
"company_id": self.env.company.id,
|
||||||
}
|
}
|
||||||
if parsed_data.get("total_experience_years"):
|
if parsed_data.get("total_experience_years"):
|
||||||
applicant_vals["exp_type"] = "experienced"
|
applicant_vals["exp_type"] = "experienced"
|
||||||
|
|
@ -612,9 +613,12 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
|
||||||
|
|
||||||
candidate = False
|
candidate = False
|
||||||
if email_value:
|
if email_value:
|
||||||
candidate = search_model.search([('id','!=',self.env.context.get('candidate_id')),'|',('email_from','=', email_value),("email_normalized", "=", email_value)], limit=1)
|
candidate = search_model.search(['|',('company_id','=',False),('company_id','in',[self.env.company.id]),('id','!=',self.env.context.get('candidate_id')),'|',('email_from','=', email_value),("email_normalized", "=", email_value)], limit=1)
|
||||||
if not candidate and phone_values:
|
if not candidate and phone_values:
|
||||||
candidate = search_model.search([
|
candidate = search_model.search([
|
||||||
|
'|',
|
||||||
|
('company_id','=',False),
|
||||||
|
('company_id','in',[self.env.company.id]),
|
||||||
"|","|",('partner_phone',"in",phone_values),
|
"|","|",('partner_phone',"in",phone_values),
|
||||||
("partner_phone_sanitized", "in", phone_values),
|
("partner_phone_sanitized", "in", phone_values),
|
||||||
("alternate_phone", "in", phone_values),
|
("alternate_phone", "in", phone_values),
|
||||||
|
|
@ -1794,8 +1798,8 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
|
||||||
job_request = False
|
job_request = False
|
||||||
if request_id:
|
if request_id:
|
||||||
job_request = job_request_model.search([("recruitment_sequence", "=", request_id)], limit=1)
|
job_request = job_request_model.search([("recruitment_sequence", "=", request_id)], limit=1)
|
||||||
if not job_request and job_title:
|
# if not job_request and job_title:
|
||||||
job_request = job_request_model.search([("name", "=ilike", job_title)], limit=1)
|
# job_request = job_request_model.search([("name", "=ilike", job_title)], limit=1)
|
||||||
if job_request:
|
if job_request:
|
||||||
return job_request, "updated"
|
return job_request, "updated"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
'version': '0.1',
|
'version': '0.1',
|
||||||
|
|
||||||
# any module necessary for this one to work correctly
|
# any module necessary for this one to work correctly
|
||||||
'depends': ['base','hr_recruitment','hr','hr_recruitment_skills','website_hr_recruitment','requisitions','search_view_extension'],
|
'depends': ['base','hr_recruitment','hr','hr_recruitment_skills','website_hr_recruitment','requisitions','search_view_extension','hr_employee_extended','website_payment'],
|
||||||
|
|
||||||
# always loaded
|
# always loaded
|
||||||
'data': [
|
'data': [
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
name="action_validate_personal_details"
|
name="action_validate_personal_details"
|
||||||
type="object"
|
type="object"
|
||||||
class="btn btn-success"
|
class="btn btn-success"
|
||||||
invisible="not employee_id">
|
invisible="personal_details_status == 'pending' or not employee_id">
|
||||||
<div>
|
<div>
|
||||||
Click here to save & Update Employee Data
|
Click here to save & Update Employee Data
|
||||||
<field name="contact_details_status"
|
<field name="contact_details_status"
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
name="action_validate_personal_details"
|
name="action_validate_personal_details"
|
||||||
type="object"
|
type="object"
|
||||||
class="btn btn-danger"
|
class="btn btn-danger"
|
||||||
invisible="employee_id">
|
invisible="personal_details_status == 'validated' or not employee_id">
|
||||||
<div>
|
<div>
|
||||||
Click here to save, Validate and Update into Employee Data
|
Click here to save, Validate and Update into Employee Data
|
||||||
<field name="personal_details_status"
|
<field name="personal_details_status"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue