Files
Odoo-Modules/fusion_payroll/static/src/js/payroll_report_action.js
2026-02-22 01:22:18 -05:00

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);