payroll changes
This commit is contained in:
parent
625bd67064
commit
5e617d3ff8
|
|
@ -1,10 +1,10 @@
|
||||||
/** @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 } from "@odoo/owl";
|
import {Component, onMounted, useRef, useState, onWillStart, onWillUpdateProps} 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";
|
||||||
import { rpc } from "@web/core/network/rpc";
|
import {rpc} from "@web/core/network/rpc";
|
||||||
|
|
||||||
export class ConsolidatedPayslipGrid extends Component {
|
export class ConsolidatedPayslipGrid extends Component {
|
||||||
static props = {
|
static props = {
|
||||||
|
|
@ -34,6 +34,12 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
console.error("Grid element not found");
|
console.error("Grid element not found");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onWillUpdateProps(async (nextProps) => {
|
||||||
|
if (nextProps.record.data.state !== this.props.record.data.state) {
|
||||||
|
await this.loadGrid();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadDependencies() {
|
async loadDependencies() {
|
||||||
|
|
@ -117,117 +123,106 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderGrid() {
|
async renderGrid() {
|
||||||
if (!this.gridRef.el) return;
|
if (!this.gridRef.el) return;
|
||||||
const columns = await this.getColumns();
|
const columns = await this.getColumns();
|
||||||
const agg = pq.aggregate;
|
const agg = pq.aggregate;
|
||||||
|
|
||||||
// Define custom aggregate functions
|
// Define custom aggregate functions
|
||||||
agg.sum_ = function(arr, col) {
|
agg.sum_ = function (arr, col) {
|
||||||
return " " + agg.sum(arr, col).toFixed(2).toString();
|
return " " + agg.sum(arr, col).toFixed(2).toString();
|
||||||
};
|
|
||||||
|
|
||||||
const groupModel = {
|
|
||||||
on: true,
|
|
||||||
//dataIndx: ["employee_code",""],
|
|
||||||
collapsed: [false, true],
|
|
||||||
merge: true,
|
|
||||||
showSummary: [true, true],
|
|
||||||
grandSummary: true,
|
|
||||||
render: () => "" // hides the group title row text
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const gridOptions = {
|
|
||||||
|
|
||||||
selectionModel: { type: 'row' },
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
groupModel: groupModel,
|
|
||||||
editable: true,
|
|
||||||
stripeRows:false,
|
|
||||||
editModel: { saveKey: $.ui.keyCode.ENTER },
|
|
||||||
filterModel: {on: true, mode: "AND", header: true, autoSearch: true, type: 'local', minLength: 1},
|
|
||||||
dataModel: {data: this.state.rows, location: "local", sorting: "local", paging: "local"},
|
|
||||||
cellSave: function (evt, ui) {
|
|
||||||
const payload = {
|
|
||||||
id: ui.rowData.id,
|
|
||||||
field: ui.dataIndx,
|
|
||||||
value: ui.newVal
|
|
||||||
};
|
};
|
||||||
updateData(payload);
|
|
||||||
},
|
|
||||||
menuIcon: true,
|
|
||||||
menuUI:{tabs: ['hideCols']},
|
|
||||||
colModel: columns,
|
|
||||||
postRenderInterval: -1,
|
|
||||||
toolbar: {
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
type: 'button',
|
|
||||||
label: 'Refresh',
|
|
||||||
icon: 'ui-icon-refresh',
|
|
||||||
listener: () => this.loadGrid()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'button',
|
|
||||||
label: 'Save Changes',
|
|
||||||
icon: 'ui-icon-disk',
|
|
||||||
listener: () => this.saveChanges()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'button',
|
|
||||||
label: 'Recalculate LOP',
|
|
||||||
icon: 'ui-icon-calculator',
|
|
||||||
listener: () => this.recalculateLOP()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'select',
|
|
||||||
label: 'Format: ',
|
|
||||||
attr: 'id="export_format"',
|
|
||||||
options: [{ xlsx: 'Excel', csv: 'Csv', htm: 'Html', json: 'Json'}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'button',
|
|
||||||
label: "Export",
|
|
||||||
icon: 'ui-icon-arrowthickstop-1-s',
|
|
||||||
listener: function () {
|
|
||||||
|
|
||||||
var format = $("#export_format").val(),
|
const groupModel = {
|
||||||
blob = this.exportData({
|
on: true,
|
||||||
//url: "/pro/demos/exportData",
|
//dataIndx: ["employee_code",""],
|
||||||
format: format,
|
collapsed: [false, true],
|
||||||
render: true
|
merge: true,
|
||||||
});
|
showSummary: [true, true],
|
||||||
if(typeof blob === "string"){
|
grandSummary: true,
|
||||||
blob = new Blob([blob]);
|
render: () => "" // hides the group title row text
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const gridOptions = {
|
||||||
|
|
||||||
|
selectionModel: {type: 'row'},
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
groupModel: groupModel,
|
||||||
|
editable: true,
|
||||||
|
stripeRows: false,
|
||||||
|
editModel: {saveKey: $.ui.keyCode.ENTER},
|
||||||
|
filterModel: {on: true, mode: "AND", header: true, autoSearch: true, type: 'local', minLength: 1},
|
||||||
|
dataModel: {data: this.state.rows, location: "local", sorting: "local", paging: "local"},
|
||||||
|
cellSave: function (evt, ui) {
|
||||||
|
const payload = {
|
||||||
|
id: ui.rowData.id,
|
||||||
|
field: ui.dataIndx,
|
||||||
|
value: ui.newVal
|
||||||
|
};
|
||||||
|
updateData(payload);
|
||||||
|
},
|
||||||
|
menuIcon: true,
|
||||||
|
menuUI: {tabs: ['hideCols']},
|
||||||
|
colModel: columns,
|
||||||
|
postRenderInterval: -1,
|
||||||
|
toolbar: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
label: 'Refresh',
|
||||||
|
icon: 'ui-icon-refresh',
|
||||||
|
listener: () => this.loadGrid()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
label: 'Format: ',
|
||||||
|
attr: 'id="export_format"',
|
||||||
|
options: [{xlsx: 'Excel', csv: 'Csv', htm: 'Html', json: 'Json'}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
label: "Export",
|
||||||
|
icon: 'ui-icon-arrowthickstop-1-s',
|
||||||
|
listener: function () {
|
||||||
|
|
||||||
|
var format = $("#export_format").val(),
|
||||||
|
blob = this.exportData({
|
||||||
|
//url: "/pro/demos/exportData",
|
||||||
|
format: format,
|
||||||
|
render: true
|
||||||
|
});
|
||||||
|
if (typeof blob === "string") {
|
||||||
|
blob = new Blob([blob]);
|
||||||
|
}
|
||||||
|
saveAs(blob, "PaySheet." + format);
|
||||||
}
|
}
|
||||||
saveAs(blob, "PaySheet."+ format );
|
},
|
||||||
}
|
]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateData(data) {
|
||||||
|
$.ajax({
|
||||||
|
url: "/slip/update",
|
||||||
|
type: "POST",
|
||||||
|
contentType: "application/json",
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success: function (response) {
|
||||||
|
console.log("Update successful:", response);
|
||||||
},
|
},
|
||||||
]
|
error: function (xhr) {
|
||||||
},
|
console.error("Update failed:", xhr.responseText);
|
||||||
};
|
}
|
||||||
function updateData(data){
|
});
|
||||||
$.ajax({
|
};
|
||||||
url: "/slip/update",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
success: function (response) {
|
|
||||||
console.log("Update successful:", response);
|
|
||||||
},
|
|
||||||
error: function (xhr) {
|
|
||||||
console.error("Update failed:", xhr.responseText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Apply CSS and initialize grid
|
// Apply CSS and initialize grid
|
||||||
$(this.gridRef.el)
|
$(this.gridRef.el)
|
||||||
.css({ height: '600px', width: '100%' })
|
.css({height: '600px', width: '100%'})
|
||||||
.pqGrid(gridOptions)
|
.pqGrid(gridOptions)
|
||||||
.pqGrid("refreshDataAndView");
|
.pqGrid("refreshDataAndView");
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshDataAndView() {
|
async refreshDataAndView() {
|
||||||
|
|
@ -293,14 +288,14 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getColumns() {
|
async getColumns() {
|
||||||
const subCols = await this.getSubgridColumns();
|
const subCols = await this.getSubgridColumns();
|
||||||
|
|
||||||
return [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "Employee",
|
title: "Employee",
|
||||||
dataIndx: "employee",
|
dataIndx: "employee",
|
||||||
width: 200,
|
width: 200,
|
||||||
filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] },
|
filter: {type: 'textbox', condition: 'contain', listeners: ['keyup']},
|
||||||
editable: false,
|
editable: false,
|
||||||
menuIcon: true,
|
menuIcon: true,
|
||||||
menuInHide: true
|
menuInHide: true
|
||||||
|
|
@ -309,7 +304,7 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
title: "Employee ID",
|
title: "Employee ID",
|
||||||
dataIndx: "employee_code",
|
dataIndx: "employee_code",
|
||||||
width: 200,
|
width: 200,
|
||||||
filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] },
|
filter: {type: 'textbox', condition: 'contain', listeners: ['keyup']},
|
||||||
editable: false,
|
editable: false,
|
||||||
menuInHide: true
|
menuInHide: true
|
||||||
},
|
},
|
||||||
|
|
@ -317,7 +312,7 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
title: "Department",
|
title: "Department",
|
||||||
dataIndx: "department",
|
dataIndx: "department",
|
||||||
width: 150,
|
width: 150,
|
||||||
filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] },
|
filter: {type: 'textbox', condition: 'contain', listeners: ['keyup']},
|
||||||
editable: false
|
editable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -372,7 +367,7 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
width: 80,
|
width: 80,
|
||||||
dataType: "integer",
|
dataType: "integer",
|
||||||
editable: (rowData) => rowData.state === 'draft',
|
editable: (rowData) => rowData.state === 'draft',
|
||||||
summary: { type: "sum_" }
|
summary: {type: "sum_"}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Sick Leave Balance",
|
title: "Sick Leave Balance",
|
||||||
|
|
@ -380,7 +375,7 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
width: 100,
|
width: 100,
|
||||||
dataType: "float",
|
dataType: "float",
|
||||||
editable: false,
|
editable: false,
|
||||||
summary: { type: "sum_" },
|
summary: {type: "sum_"},
|
||||||
format: "##,##0.00"
|
format: "##,##0.00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -389,7 +384,7 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
width: 100,
|
width: 100,
|
||||||
dataType: "float",
|
dataType: "float",
|
||||||
editable: false,
|
editable: false,
|
||||||
summary: { type: "sum_" },
|
summary: {type: "sum_"},
|
||||||
format: "##,##0.00"
|
format: "##,##0.00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -398,7 +393,7 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
width: 100,
|
width: 100,
|
||||||
dataType: "float",
|
dataType: "float",
|
||||||
editable: false,
|
editable: false,
|
||||||
summary: { type: "sum_" },
|
summary: {type: "sum_"},
|
||||||
format: "##,##0.00"
|
format: "##,##0.00"
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
|
|
@ -438,67 +433,77 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
return `<span class="badge badge-${state === 'done' ? 'success' : 'warning'}">${state}</span>`;
|
return `<span class="badge badge-${state === 'done' ? 'success' : 'warning'}">${state}</span>`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...subCols,
|
...subCols,
|
||||||
{
|
{
|
||||||
title: "View",
|
title: "View",
|
||||||
width: 120,
|
width: 120,
|
||||||
editable: false,
|
editable: false,
|
||||||
summary:false,
|
summary: false,
|
||||||
render: function (ui) {
|
render: function (ui) {
|
||||||
return "<button class='row-btn-view' type='button' >View</button>"
|
return "<button class='row-btn-view' type='button' >View</button>"
|
||||||
},
|
},
|
||||||
postRender: function (ui) {
|
postRender: function (ui) {
|
||||||
var grid = this,
|
var grid = this,
|
||||||
$cell = grid.getCell(ui);
|
$cell = grid.getCell(ui);
|
||||||
$cell.find(".row-btn-view")
|
$cell.find(".row-btn-view")
|
||||||
.button({ icons: { primary: 'ui-icon-extlink'} })
|
.button({icons: {primary: 'ui-icon-extlink'}})
|
||||||
.on("click", async function (evt) {
|
.on("click", async function (evt) {
|
||||||
const res = await odoo.__WOWL_DEBUG__.root.orm.call('hr.payslip','action_open_payslips',[ui.rowData.id])
|
const res = await odoo.__WOWL_DEBUG__.root.orm.call('hr.payslip', 'action_open_payslips', [ui.rowData.id])
|
||||||
// res.views = [[false, "form"]],
|
// res.views = [[false, "form"]],
|
||||||
await odoo.__WOWL_DEBUG__.root.actionService.doAction(res)
|
await odoo.__WOWL_DEBUG__.root.actionService.doAction(res)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Edit",
|
];
|
||||||
width: 120,
|
if (this.props.record.data.state !== "paid") {
|
||||||
editable: false,
|
columns.push(
|
||||||
render: function (ui) {
|
{
|
||||||
return "<button class='row-btn-edit' type='button'>Edit</button>"
|
title: "Edit",
|
||||||
},
|
width: 120,
|
||||||
postRender: function (ui) {
|
editable: false,
|
||||||
|
render: function (ui) {
|
||||||
|
if (ui.rowData.state == 'paid') {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "<button class='row-btn-edit' type='button'>Edit</button>"
|
||||||
|
},
|
||||||
|
postRender: function (ui) {
|
||||||
var grid = this,
|
var grid = this,
|
||||||
$cell = grid.getCell(ui);
|
$cell = grid.getCell(ui);
|
||||||
$cell.find(".row-btn-edit")
|
$cell.find(".row-btn-edit")
|
||||||
.button({ icons: { primary: 'ui-icon-pencil'} })
|
.button({icons: {primary: 'ui-icon-pencil'}})
|
||||||
.on("click", async function (evt) {
|
.on("click", async function (evt) {
|
||||||
const res = await odoo.__WOWL_DEBUG__.root.orm.call('hr.payslip','action_edit_payslip_lines',[ui.rowData.id])
|
const res = await odoo.__WOWL_DEBUG__.root.orm.call('hr.payslip', 'action_edit_payslip_lines', [ui.rowData.id])
|
||||||
res.views = [[false, "form"]],
|
res.views = [[false, "form"]],
|
||||||
await odoo.__WOWL_DEBUG__.root.actionService.doAction(res)
|
await odoo.__WOWL_DEBUG__.root.actionService.doAction(res)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSubgridColumns() {
|
async getSubgridColumns() {
|
||||||
const response = await this.orm.call(
|
const response = await this.orm.call(
|
||||||
"hr.payslip.run",
|
"hr.payslip.run",
|
||||||
"sub_columns",
|
"sub_columns",
|
||||||
[this.state.payslipRunId]
|
[this.state.payslipRunId]
|
||||||
);
|
);
|
||||||
return response || [];
|
return response || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshGrid() {
|
async refreshGrid() {
|
||||||
try {
|
try {
|
||||||
await this.loadGridData();
|
await this.loadGridData();
|
||||||
$(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) {
|
||||||
console.error("Error refreshing grid:", error);
|
console.error("Error refreshing grid:", error);
|
||||||
this.notification.add("Error refreshing data", { type: 'danger' });
|
this.notification.add("Error refreshing data", {type: 'danger'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -513,10 +518,10 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
[this.state.payslipRunId, updatedData]
|
[this.state.payslipRunId, updatedData]
|
||||||
);
|
);
|
||||||
await this.refreshGrid();
|
await this.refreshGrid();
|
||||||
this.notification.add("Changes saved successfully", { type: 'success' });
|
this.notification.add("Changes saved successfully", {type: 'success'});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving data:", error);
|
console.error("Error saving data:", error);
|
||||||
this.notification.add("Error saving changes", { type: 'danger' });
|
this.notification.add("Error saving changes", {type: 'danger'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -528,10 +533,10 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
[this.state.payslipRunId]
|
[this.state.payslipRunId]
|
||||||
);
|
);
|
||||||
await this.refreshGrid();
|
await this.refreshGrid();
|
||||||
this.notification.add("LOP days recalculated successfully", { type: 'success' });
|
this.notification.add("LOP days recalculated successfully", {type: 'success'});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error recalculating LOP:", error);
|
console.error("Error recalculating LOP:", error);
|
||||||
this.notification.add("Error recalculating LOP", { type: 'danger' });
|
this.notification.add("Error recalculating LOP", {type: 'danger'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -543,10 +548,10 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
[this.state.payslipRunId]
|
[this.state.payslipRunId]
|
||||||
);
|
);
|
||||||
await this.refreshGrid();
|
await this.refreshGrid();
|
||||||
this.notification.add("All records validated successfully", { type: 'success' });
|
this.notification.add("All records validated successfully", {type: 'success'});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error validating records:", error);
|
console.error("Error validating records:", error);
|
||||||
this.notification.add("Error validating records", { type: 'danger' });
|
this.notification.add("Error validating records", {type: 'danger'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,6 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td style="border: 1px solid #ccc; padding: 6px;" colspan="3"><strong>Gross Salary</strong></td>
|
<td style="border: 1px solid #ccc; padding: 6px;" colspan="3"><strong>Gross Salary</strong></td>
|
||||||
<td style="border: 1px solid #ccc; padding: 6px; text-align: right;" colspan="1"><strong><t t-esc="'%.2f' % income"/></strong></td>
|
<td style="border: 1px solid #ccc; padding: 6px; text-align: right;" colspan="1"><strong><t t-esc="'%.2f' % income"/></strong></td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="border: 1px solid #ccc; padding: 6px;" colspan="3"><strong>Total Deduction</strong></td>
|
<td style="border: 1px solid #ccc; padding: 6px;" colspan="3"><strong>Total Deduction</strong></td>
|
||||||
|
|
|
||||||
|
|
@ -74,10 +74,10 @@
|
||||||
<page name="salary_rules" string="Salary Rules">
|
<page name="salary_rules" string="Salary Rules">
|
||||||
<field name="rule_ids" readonly="not id">
|
<field name="rule_ids" readonly="not id">
|
||||||
<list>
|
<list>
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="code"/>
|
<field name="code"/>
|
||||||
<field name="category_id"/>
|
<field name="category_id"/>
|
||||||
<field name="sequence" column_invisible="True"/>
|
|
||||||
<field name="partner_id"/>
|
<field name="partner_id"/>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,21 @@
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'application': True,
|
'application': True,
|
||||||
'depends': [
|
'depends': [
|
||||||
'hr_payroll'
|
'hr_payroll',
|
||||||
|
'consolidated_batch_payslip',
|
||||||
|
'hr_work_entry_holidays'
|
||||||
],
|
],
|
||||||
'data': [
|
'data': [
|
||||||
'data/hr_salary_advance_sequence.xml',
|
'data/hr_salary_advance_sequence.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
|
'views/hr_payslip_employees_views.xml',
|
||||||
'views/hr_salary_advance_views.xml',
|
'views/hr_salary_advance_views.xml',
|
||||||
'views/menus.xml'
|
'views/menus.xml',
|
||||||
|
'views/hr_work_entry_views.xml',
|
||||||
],
|
],
|
||||||
'assets': {
|
'assets': {
|
||||||
|
'web.assets_backend': [
|
||||||
|
'hr_payroll_extended/static/src/js/consolidated_payslip_grid_patch.js',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
|
from . import hr_work_entry
|
||||||
from . import hr_salary_advance
|
from . import hr_salary_advance
|
||||||
|
from . import hr_payslip_employees
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class HrPayslipEmployees(models.TransientModel):
|
||||||
|
_inherit = "hr.payslip.employees"
|
||||||
|
|
||||||
|
conflict_work_entry_ids = fields.Many2many(
|
||||||
|
"hr.work.entry",
|
||||||
|
compute="_compute_conflict_work_entry_ids",
|
||||||
|
inverse="_inverse_conflict_work_entry_ids",
|
||||||
|
string="Work Entry Conflicts",
|
||||||
|
readonly=False,
|
||||||
|
)
|
||||||
|
conflict_work_entry_count = fields.Integer(
|
||||||
|
compute="_compute_conflict_work_entry_ids",
|
||||||
|
string="Conflict Count",
|
||||||
|
)
|
||||||
|
timeoff_differ_ids = fields.Many2many(
|
||||||
|
"hr.leave",
|
||||||
|
compute="_compute_differ_timeoff_ids",
|
||||||
|
inverse="_inverse_differ_timeoff_ids",
|
||||||
|
string="Work Entry Conflicts",
|
||||||
|
readonly=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_generation_dates(self):
|
||||||
|
self.ensure_one()
|
||||||
|
payslip_run = self.env["hr.payslip.run"].browse(self.env.context.get("active_id")).exists()
|
||||||
|
if payslip_run:
|
||||||
|
return payslip_run.date_start, payslip_run.date_end
|
||||||
|
return (
|
||||||
|
fields.Date.to_date(self.env.context.get("default_date_start")),
|
||||||
|
fields.Date.to_date(self.env.context.get("default_date_end")),
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends("employee_ids")
|
||||||
|
def _compute_differ_timeoff_ids(self):
|
||||||
|
DifferLeaves = self.env["hr.leave"]
|
||||||
|
for wizard in self:
|
||||||
|
date_start, date_end = wizard._get_generation_dates()
|
||||||
|
if not wizard.employee_ids or not date_start or not date_end:
|
||||||
|
conflicts = DifferLeaves
|
||||||
|
else:
|
||||||
|
conflicts = DifferLeaves.search([
|
||||||
|
("employee_id", "in", wizard.employee_ids.ids),
|
||||||
|
("payslip_state", "=", "blocked"),
|
||||||
|
("date_from", "<=", date_end + relativedelta(days=1)),
|
||||||
|
("date_to", ">=", date_start + relativedelta(days=-1)),
|
||||||
|
])
|
||||||
|
wizard.timeoff_differ_ids = conflicts
|
||||||
|
|
||||||
|
def _inverse_differ_timeoff_ids(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
@api.depends("employee_ids")
|
||||||
|
def _compute_conflict_work_entry_ids(self):
|
||||||
|
WorkEntry = self.env["hr.work.entry"]
|
||||||
|
for wizard in self:
|
||||||
|
date_start, date_end = wizard._get_generation_dates()
|
||||||
|
if not wizard.employee_ids or not date_start or not date_end:
|
||||||
|
conflicts = WorkEntry
|
||||||
|
else:
|
||||||
|
conflicts = WorkEntry.search([
|
||||||
|
("employee_id", "in", wizard.employee_ids.ids),
|
||||||
|
("state", "=", "conflict"),
|
||||||
|
("date_start", "<=", date_end + relativedelta(days=1)),
|
||||||
|
("date_stop", ">=", date_start + relativedelta(days=-1)),
|
||||||
|
])
|
||||||
|
wizard.conflict_work_entry_ids = conflicts
|
||||||
|
wizard.conflict_work_entry_count = len(conflicts)
|
||||||
|
|
||||||
|
def _inverse_conflict_work_entry_ids(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
def action_refresh_conflict_work_entries(self):
|
||||||
|
self.invalidate_recordset(["conflict_work_entry_ids", "conflict_work_entry_count"])
|
||||||
|
return {
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"res_model": self._name,
|
||||||
|
"res_id": self.id,
|
||||||
|
"view_mode": "form",
|
||||||
|
"target": "new",
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
from odoo import models
|
||||||
|
|
||||||
|
class HrWorkEntry(models.Model):
|
||||||
|
_inherit = 'hr.work.entry'
|
||||||
|
|
||||||
|
def action_open_conflict(self):
|
||||||
|
self.ensure_one()
|
||||||
|
return {
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"name": "Work Entry Conflict",
|
||||||
|
"res_model": "hr.work.entry",
|
||||||
|
"res_id": self.id,
|
||||||
|
"view_mode": "form",
|
||||||
|
"target": "new", # popup
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,215 @@
|
||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
import { useService } from "@web/core/utils/hooks";
|
||||||
|
import { ConsolidatedPayslipGrid } from "@consolidated_batch_payslip/components/pqgrid_batch_payslip/pqgrid_batch_payslip";
|
||||||
|
|
||||||
|
patch(ConsolidatedPayslipGrid.prototype, {
|
||||||
|
setup() {
|
||||||
|
super.setup(...arguments);
|
||||||
|
this.notification = useService("notification");
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadGrid() {
|
||||||
|
try {
|
||||||
|
if (!this.state.payslipRunId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const records = await this.orm.call(
|
||||||
|
"hr.payslip.run",
|
||||||
|
"get_consolidated_attendance_data",
|
||||||
|
[this.state.payslipRunId]
|
||||||
|
);
|
||||||
|
this.state.rows = records.map((rec) => {
|
||||||
|
const row = {
|
||||||
|
id: rec.id,
|
||||||
|
employee_id: rec.employee_id[0],
|
||||||
|
employee: rec.employee_id[1],
|
||||||
|
employee_code: rec.employee_code || "N/A",
|
||||||
|
department: rec.department_id ? rec.department_id[1] : "N/A",
|
||||||
|
total_days: rec.total_days || 0,
|
||||||
|
worked_days: rec.worked_days || 0,
|
||||||
|
attendance_days: rec.attendance_days || 0,
|
||||||
|
leave_days: rec.leave_days || 0,
|
||||||
|
lop_days: rec.lop_days || 0,
|
||||||
|
sick_leave_balance: rec.sick_leave_balance || 0,
|
||||||
|
casual_leave_balance: rec.casual_leave_balance || 0,
|
||||||
|
privilege_leave_balance: rec.privilege_leave_balance || 0,
|
||||||
|
sick_leave_taken: rec.sick_leave_taken || 0,
|
||||||
|
casual_leave_taken: rec.casual_leave_taken || 0,
|
||||||
|
privilege_leave_taken: rec.privilege_leave_taken || 0,
|
||||||
|
state: rec.state || "draft",
|
||||||
|
doj: rec.doj,
|
||||||
|
bank: rec.bank,
|
||||||
|
birthday: rec.birthday,
|
||||||
|
lines: rec.lines || [],
|
||||||
|
};
|
||||||
|
for (const line of rec.lines || []) {
|
||||||
|
if (line.code) {
|
||||||
|
row[line.code] = line.amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
|
||||||
|
const grid = this._getGridInstance();
|
||||||
|
if (grid) {
|
||||||
|
grid.option("dataModel.data", this.state.rows);
|
||||||
|
grid.refreshDataAndView();
|
||||||
|
this._wireToolbarActions();
|
||||||
|
} else {
|
||||||
|
await this.renderGrid();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading data:", error);
|
||||||
|
this.showNotification("Error loading grid data", "danger");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async renderGrid() {
|
||||||
|
await super.renderGrid(...arguments);
|
||||||
|
this._wireToolbarActions();
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshGrid() {
|
||||||
|
await this.loadGrid();
|
||||||
|
this.showNotification("Data refreshed successfully");
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveChanges() {
|
||||||
|
const grid = this._getGridInstance();
|
||||||
|
const updatedData = grid.option("dataModel.data");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.orm.call(
|
||||||
|
"hr.payslip.run",
|
||||||
|
"save_consolidated_attendance_data",
|
||||||
|
[this.state.payslipRunId, updatedData]
|
||||||
|
);
|
||||||
|
await this.refreshGrid();
|
||||||
|
this.showNotification("Changes saved successfully");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error saving data:", error);
|
||||||
|
this.showNotification("Error saving changes", "danger");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async recalculateLOP() {
|
||||||
|
try {
|
||||||
|
await this.orm.call("hr.payslip.run", "recalculate_lop_days", [this.state.payslipRunId]);
|
||||||
|
await this.refreshGrid();
|
||||||
|
this.showNotification("LOP days recalculated successfully");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error recalculating LOP:", error);
|
||||||
|
this.showNotification("Error recalculating LOP", "danger");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showNotification(message, type = "success") {
|
||||||
|
if (this.notification) {
|
||||||
|
this.notification.add(message, { type });
|
||||||
|
} else {
|
||||||
|
console.log(`${type}: ${message}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_wireToolbarActions() {
|
||||||
|
if (!this._getGridInstance()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const $grid = $(this.gridRef.el);
|
||||||
|
$grid.find("button").filter((_, button) => button.textContent.trim() === "Refresh")
|
||||||
|
.off("click.hr_payroll_extended")
|
||||||
|
.on("click.hr_payroll_extended", (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopImmediatePropagation();
|
||||||
|
this.refreshGrid();
|
||||||
|
});
|
||||||
|
$grid.find("button").filter((_, button) => button.textContent.trim() === "Export")
|
||||||
|
.off("click.hr_payroll_extended")
|
||||||
|
.on("click.hr_payroll_extended", (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopImmediatePropagation();
|
||||||
|
this._exportGridData();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_exportGridData() {
|
||||||
|
const format = $("#export_format").val() || "csv";
|
||||||
|
const filename = `PaySheet.${format === "xlsx" ? "xls" : format}`;
|
||||||
|
try {
|
||||||
|
const blob = $(this.gridRef.el).pqGrid("exportData", { format, render: true });
|
||||||
|
if (blob) {
|
||||||
|
this._saveBlob(blob, `PaySheet.${format}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("pqGrid exportData failed, using fallback export.", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const grid = this._getGridInstance();
|
||||||
|
const rows = grid.option("dataModel.data") || [];
|
||||||
|
const columns = (grid.option("colModel") || []).filter((col) => col.dataIndx);
|
||||||
|
const content = this._buildExportContent(format, rows, columns);
|
||||||
|
this._saveBlob(content, filename);
|
||||||
|
},
|
||||||
|
|
||||||
|
_buildExportContent(format, rows, columns) {
|
||||||
|
if (format === "json") {
|
||||||
|
return new Blob([JSON.stringify(rows, null, 2)], { type: "application/json;charset=utf-8" });
|
||||||
|
}
|
||||||
|
if (format === "htm" || format === "xlsx") {
|
||||||
|
const header = columns.map((col) => `<th>${this._escapeHtml(col.title || col.dataIndx)}</th>`).join("");
|
||||||
|
const body = rows.map((row) => (
|
||||||
|
`<tr>${columns.map((col) => `<td>${this._escapeHtml(row[col.dataIndx] ?? "")}</td>`).join("")}</tr>`
|
||||||
|
)).join("");
|
||||||
|
return new Blob([
|
||||||
|
`<table><thead><tr>${header}</tr></thead><tbody>${body}</tbody></table>`
|
||||||
|
], { type: "application/vnd.ms-excel;charset=utf-8" });
|
||||||
|
}
|
||||||
|
const csv = [
|
||||||
|
columns.map((col) => this._csvCell(col.title || col.dataIndx)).join(","),
|
||||||
|
...rows.map((row) => columns.map((col) => this._csvCell(row[col.dataIndx])).join(",")),
|
||||||
|
].join("\n");
|
||||||
|
return new Blob([csv], { type: "text/csv;charset=utf-8" });
|
||||||
|
},
|
||||||
|
|
||||||
|
_csvCell(value) {
|
||||||
|
return `"${String(value ?? "").replaceAll('"', '""')}"`;
|
||||||
|
},
|
||||||
|
|
||||||
|
_escapeHtml(value) {
|
||||||
|
return String(value)
|
||||||
|
.replaceAll("&", "&")
|
||||||
|
.replaceAll("<", "<")
|
||||||
|
.replaceAll(">", ">")
|
||||||
|
.replaceAll('"', """)
|
||||||
|
.replaceAll("'", "'");
|
||||||
|
},
|
||||||
|
|
||||||
|
_saveBlob(content, filename) {
|
||||||
|
const blob = typeof content === "string" ? new Blob([content]) : content;
|
||||||
|
if (window.saveAs) {
|
||||||
|
window.saveAs(blob, filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = url;
|
||||||
|
link.download = filename;
|
||||||
|
link.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getGridInstance() {
|
||||||
|
if (!this.gridRef.el || !window.$) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return $(this.gridRef.el).pqGrid("instance");
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_hr_payslip_by_employees_inherit_conflicts" model="ir.ui.view">
|
||||||
|
<field name="name">hr.payslip.employees.form.inherit.conflict.work.entries</field>
|
||||||
|
<field name="model">hr.payslip.employees</field>
|
||||||
|
<field name="inherit_id" ref="hr_payroll.view_hr_payslip_by_employees"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//form/field[@name='employee_ids']" position="replace">
|
||||||
|
<notebook>
|
||||||
|
<page string="Employees">
|
||||||
|
<field name="employee_ids" nolabel="1" widget="employee_line_many2many" context="{'list_view_ref' : 'hr_payroll.hr_payroll_employee_tree_inherit'}">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="work_email"/>
|
||||||
|
<field name="department_id"/>
|
||||||
|
<field name="job_id"/>
|
||||||
|
<field name="structure_type_id"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Work Entry Conflicts">
|
||||||
|
<group>
|
||||||
|
<field name="conflict_work_entry_count" readonly="1"/>
|
||||||
|
<!-- <button name="action_refresh_conflict_work_entries" type="object" string="Refresh Conflicts" class="btn-secondary"/>-->
|
||||||
|
</group>
|
||||||
|
<field name="conflict_work_entry_ids" nolabel="1" readonly="1" context="{'default_state': 'draft'}">
|
||||||
|
<list editable="bottom" multi_edit="1" create="0" delete="0" edit="0">
|
||||||
|
<field name="employee_id" readonly="1"/>
|
||||||
|
<field name="name" string="Description"/>
|
||||||
|
<field name="work_entry_type_id" options="{'no_create': True, 'no_open': True}"/>
|
||||||
|
<field name="date_start"/>
|
||||||
|
<field name="date_stop"/>
|
||||||
|
<field name="duration" widget="float_time" readonly="1"/>
|
||||||
|
<field name="state" readonly="1"/>
|
||||||
|
<button name="action_open_conflict"
|
||||||
|
type="object"
|
||||||
|
string="Open"
|
||||||
|
icon="fa-external-link"/>
|
||||||
|
<!-- <button name="action_validate" type="object" string="Validate" class="btn-primary"/>-->
|
||||||
|
</list>
|
||||||
|
<form string="Work Entry Conflict">
|
||||||
|
<header>
|
||||||
|
<button name="action_validate" type="object" string="Validate" class="btn-primary"/>
|
||||||
|
<field name="state" widget="statusbar" readonly="1" statusbar_visible="draft,validated,conflict"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="name" string="Description"/>
|
||||||
|
<field name="employee_id" readonly="1" widget="many2one_avatar_user"/>
|
||||||
|
<field name="work_entry_type_id" options="{'no_create': True, 'no_open': True}"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="date_start"/>
|
||||||
|
<field name="date_stop"/>
|
||||||
|
<field name="duration" widget="float_time" readonly="1"/>
|
||||||
|
<field name="company_id" invisible="1"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Time Off Defer">
|
||||||
|
<field name="timeoff_differ_ids" nolabel="1" readonly="1">
|
||||||
|
<list editable="bottom" multi_edit="1" create="0" delete="0" edit="0">
|
||||||
|
<field name="employee_id" readonly="state in ['cancel', 'refuse', 'validate', 'validate1']" on_change="1"/>
|
||||||
|
<field name="department_id" optional="hidden" readonly="state in ['cancel', 'refuse', 'validate', 'validate1']"/>
|
||||||
|
<field name="holiday_status_id" class="fw-bold" readonly="state in ['cancel', 'refuse', 'validate', 'validate1']" on_change="1"/>
|
||||||
|
<field name="name" optional="hidden"/>
|
||||||
|
<field name="date_from" readonly="state in ['cancel', 'refuse', 'validate', 'validate1']" on_change="1"/>
|
||||||
|
<field name="date_to" readonly="state in ['cancel', 'refuse', 'validate', 'validate1']" on_change="1"/>
|
||||||
|
<field name="submitted_date" readonly="1" force_save="1" optional="hidden"/>
|
||||||
|
<field name="duration_display" string="Duration"/>
|
||||||
|
<field name="state" widget="badge" decoration-warning="state in ('confirm','validate1')" decoration-success="state == 'validate'" decoration-danger="state == 'cancel'"/>
|
||||||
|
<field name="payslip_state" widget="state_selection" options="{'hide_label': False}"/>
|
||||||
|
<field name="active_employee" column_invisible="True"/>
|
||||||
|
<field name="user_id" column_invisible="True"/>
|
||||||
|
<field name="message_needaction" column_invisible="True"/>
|
||||||
|
<!-- <button string="Approve" name="action_approve" type="object" icon="fa-thumbs-up" invisible="state != 'confirm'"/>-->
|
||||||
|
<field name="company_id" optional="hidden" column_invisible="True"/>
|
||||||
|
<!-- <button name="action_validate" type="object" string="Validate" class="btn-primary"/>-->
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
</notebook>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="payroll_hr_work_entry_form_view_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">payroll.hr.work.entry.view.form.inherit</field>
|
||||||
|
<field name="model">hr.work.entry</field>
|
||||||
|
<field name="inherit_id" ref="hr_work_entry.hr_work_entry_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='state']" position="before">
|
||||||
|
<field name="leave_state" invisible="1"/>
|
||||||
|
<button string="Refuse Time Off" name="action_refuse_leave" type="object" invisible="state != 'conflict' or not leave_id"/>
|
||||||
|
<button string="Approve Time Off" name="action_approve_leave" type="object" invisible="state != 'conflict' or not leave_id or leave_state == 'validate'"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='work_entry_type_id']" position="after">
|
||||||
|
<field name="leave_id" invisible="not leave_id"/>
|
||||||
|
<field name="contract_id" invisible="1"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="payroll_hr_work_entry_form_view_inherit_contract" model="ir.ui.view">
|
||||||
|
<field name="name">payroll.hr.work.entry.view.form.inherit.contract</field>
|
||||||
|
<field name="model">hr.work.entry</field>
|
||||||
|
<field name="inherit_id" ref="hr_work_entry_contract.hr_work_entry_contract_view_form_inherit"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//div[@name='work_entry_undefined']" position="after">
|
||||||
|
<div invisible="not work_entry_type_id">
|
||||||
|
<div class="alert alert-warning" role="alert" invisible="not leave_id">
|
||||||
|
This work entry cannot be validated. There is a leave to approve (or refuse) at the same time.
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-warning" role="alert" invisible="leave_id">
|
||||||
|
This work entry cannot be validated. It is conflicting with at least one work entry. <br/>
|
||||||
|
Two work entries of the same employee cannot overlap at the same time.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="payroll_leave_hr_work_entry_type_form_view_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">payroll.leave.hr.work.entry.type.view.form.inherit</field>
|
||||||
|
<field name="model">hr.work.entry.type</field>
|
||||||
|
<field name="inherit_id" ref="hr_work_entry.hr_work_entry_type_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<group name="time_off" position="inside">
|
||||||
|
<field name="leave_type_ids" widget="many2many_tags" invisible="not is_leave"/>
|
||||||
|
</group>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in New Issue