267 lines
8.9 KiB
JavaScript
267 lines
8.9 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { registry } from "@web/core/registry";
|
|
import { Component, useState, onWillStart } from "@odoo/owl";
|
|
import { useService } from "@web/core/utils/hooks";
|
|
import { rpc } from "@web/core/network/rpc";
|
|
import { _t } from "@web/core/l10n/translation";
|
|
|
|
/**
|
|
* PayrollReportAction - Main OWL component for payroll reports
|
|
* Provides QuickBooks-style filtering, viewing, and export functionality.
|
|
*/
|
|
export class PayrollReportAction extends Component {
|
|
static template = "fusion_payroll.PayrollReportAction";
|
|
static props = {
|
|
action: { type: Object },
|
|
actionId: { type: [Number, Boolean], optional: true },
|
|
};
|
|
|
|
setup() {
|
|
this.orm = useService("orm");
|
|
this.actionService = useService("action");
|
|
this.notification = useService("notification");
|
|
|
|
// Get report model from action context
|
|
this.reportModel = this.props.action.context?.report_model || 'payroll.report.paycheque.history';
|
|
|
|
this.state = useState({
|
|
loading: true,
|
|
reportName: '',
|
|
columns: [],
|
|
lines: [],
|
|
options: {},
|
|
dateFilterOptions: [],
|
|
filterDateRange: true,
|
|
filterEmployee: true,
|
|
filterDepartment: false,
|
|
selectedDateFilter: 'this_year',
|
|
dateFrom: '',
|
|
dateTo: '',
|
|
showCustomDate: false,
|
|
sortColumn: null,
|
|
sortDirection: 'asc',
|
|
expandedLines: {},
|
|
});
|
|
|
|
onWillStart(async () => {
|
|
await this.loadReportData();
|
|
});
|
|
}
|
|
|
|
async loadReportData(options = null) {
|
|
this.state.loading = true;
|
|
try {
|
|
const result = await rpc("/payroll/report/get_report_data", {
|
|
report_model: this.reportModel,
|
|
options: options || this.state.options,
|
|
});
|
|
|
|
if (result.error) {
|
|
console.error("Report error:", result.error, result.traceback);
|
|
this.notification.add(_t("Error loading report: %s", result.error), { type: "danger" });
|
|
this.state.loading = false;
|
|
return;
|
|
}
|
|
|
|
this.state.reportName = result.report_name;
|
|
this.state.columns = result.columns || [];
|
|
this.state.lines = result.lines || [];
|
|
this.state.options = result.options || {};
|
|
this.state.dateFilterOptions = result.date_filter_options || [];
|
|
this.state.filterDateRange = result.filter_date_range !== undefined ? result.filter_date_range : true;
|
|
this.state.filterEmployee = result.filter_employee !== undefined ? result.filter_employee : true;
|
|
this.state.filterDepartment = result.filter_department !== undefined ? result.filter_department : false;
|
|
|
|
// Set date filter values
|
|
if (result.options && result.options.date) {
|
|
this.state.selectedDateFilter = result.options.date.filter;
|
|
this.state.dateFrom = result.options.date.date_from;
|
|
this.state.dateTo = result.options.date.date_to;
|
|
this.state.showCustomDate = result.options.date.filter === 'custom';
|
|
}
|
|
} catch (error) {
|
|
console.error("Error loading report:", error);
|
|
this.notification.add(_t("Error loading report: %s", error.message || error), { type: "danger" });
|
|
}
|
|
this.state.loading = false;
|
|
}
|
|
|
|
async onDateFilterChange(ev) {
|
|
const filter = ev.target.value;
|
|
this.state.selectedDateFilter = filter;
|
|
this.state.showCustomDate = filter === 'custom';
|
|
|
|
if (filter !== 'custom') {
|
|
await this.applyFilters();
|
|
}
|
|
}
|
|
|
|
async onDateFromChange(ev) {
|
|
this.state.dateFrom = ev.target.value;
|
|
}
|
|
|
|
async onDateToChange(ev) {
|
|
this.state.dateTo = ev.target.value;
|
|
}
|
|
|
|
async applyFilters() {
|
|
const options = {
|
|
...this.state.options,
|
|
date: {
|
|
filter: this.state.selectedDateFilter,
|
|
date_from: this.state.dateFrom,
|
|
date_to: this.state.dateTo,
|
|
},
|
|
};
|
|
await this.loadReportData(options);
|
|
}
|
|
|
|
sortByColumn(column) {
|
|
if (!column.sortable) return;
|
|
|
|
if (this.state.sortColumn === column.field) {
|
|
this.state.sortDirection = this.state.sortDirection === 'asc' ? 'desc' : 'asc';
|
|
} else {
|
|
this.state.sortColumn = column.field;
|
|
this.state.sortDirection = 'asc';
|
|
}
|
|
|
|
// Sort lines
|
|
const sortedLines = [...this.state.lines].sort((a, b) => {
|
|
const aVal = a.values?.[column.field] ?? '';
|
|
const bVal = b.values?.[column.field] ?? '';
|
|
|
|
// Keep totals at top/bottom
|
|
if (a.level < 0) return -1;
|
|
if (b.level < 0) return 1;
|
|
|
|
let comparison = 0;
|
|
if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
comparison = aVal - bVal;
|
|
} else {
|
|
comparison = String(aVal).localeCompare(String(bVal));
|
|
}
|
|
|
|
return this.state.sortDirection === 'asc' ? comparison : -comparison;
|
|
});
|
|
|
|
this.state.lines = sortedLines;
|
|
}
|
|
|
|
async toggleLine(lineId) {
|
|
if (this.state.expandedLines[lineId]) {
|
|
delete this.state.expandedLines[lineId];
|
|
} else {
|
|
this.state.expandedLines[lineId] = true;
|
|
// Load detail lines if needed
|
|
await this.loadDetailLines(lineId);
|
|
}
|
|
}
|
|
|
|
async loadDetailLines(lineId) {
|
|
try {
|
|
const result = await rpc("/payroll/report/get_detail_lines", {
|
|
report_model: this.reportModel,
|
|
line_id: lineId,
|
|
options: this.state.options,
|
|
});
|
|
|
|
if (result.error) {
|
|
console.error("Detail lines error:", result.error);
|
|
return;
|
|
}
|
|
|
|
if (result.detail_lines && result.detail_lines.length > 0) {
|
|
// Insert detail lines after the parent line
|
|
const lineIndex = this.state.lines.findIndex(l => l.id === lineId);
|
|
if (lineIndex >= 0) {
|
|
const newLines = [...this.state.lines];
|
|
newLines.splice(lineIndex + 1, 0, ...result.detail_lines);
|
|
this.state.lines = newLines;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Error loading detail lines:", error);
|
|
}
|
|
}
|
|
|
|
async exportXlsx() {
|
|
const options = JSON.stringify(this.state.options);
|
|
const url = `/payroll/report/export_xlsx?report_model=${this.reportModel}&options=${encodeURIComponent(options)}`;
|
|
window.location.href = url;
|
|
}
|
|
|
|
async exportPdf() {
|
|
const options = JSON.stringify(this.state.options);
|
|
const url = `/payroll/report/export_pdf?report_model=${this.reportModel}&options=${encodeURIComponent(options)}`;
|
|
window.open(url, '_blank');
|
|
}
|
|
|
|
async openRecord(line) {
|
|
if (line.model && line.res_id) {
|
|
await this.actionService.doAction({
|
|
type: 'ir.actions.act_window',
|
|
res_model: line.model,
|
|
res_id: line.res_id,
|
|
views: [[false, 'form']],
|
|
target: 'current',
|
|
});
|
|
}
|
|
}
|
|
|
|
formatValue(value, columnType) {
|
|
if (value === null || value === undefined || value === '') {
|
|
return '';
|
|
}
|
|
|
|
switch (columnType) {
|
|
case 'monetary':
|
|
return new Intl.NumberFormat('en-CA', {
|
|
style: 'currency',
|
|
currency: 'CAD',
|
|
}).format(value);
|
|
case 'float':
|
|
return new Intl.NumberFormat('en-CA', {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
}).format(value);
|
|
case 'integer':
|
|
return Math.round(value).toString();
|
|
case 'date':
|
|
if (value) {
|
|
return new Date(value).toLocaleDateString('en-CA');
|
|
}
|
|
return '';
|
|
case 'percentage':
|
|
return `${(value * 100).toFixed(2)}%`;
|
|
default:
|
|
return String(value);
|
|
}
|
|
}
|
|
|
|
getLineClass(line) {
|
|
const classes = [];
|
|
if (line.class) {
|
|
classes.push(line.class);
|
|
}
|
|
if (line.level < 0) {
|
|
classes.push('o_payroll_report_total');
|
|
}
|
|
if (line.level > 0) {
|
|
classes.push('o_payroll_report_detail');
|
|
}
|
|
return classes.join(' ');
|
|
}
|
|
|
|
goBack() {
|
|
this.actionService.doAction({
|
|
type: 'ir.actions.client',
|
|
tag: 'fusion_payroll.report_hub',
|
|
});
|
|
}
|
|
}
|
|
|
|
// Register the component as a client action
|
|
registry.category("actions").add("fusion_payroll.payroll_report_action", PayrollReportAction);
|