Initial commit
This commit is contained in:
266
fusion_payroll/static/src/js/payroll_report_action.js
Normal file
266
fusion_payroll/static/src/js/payroll_report_action.js
Normal file
@@ -0,0 +1,266 @@
|
||||
/** @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);
|
||||
48
fusion_payroll/static/src/js/report_hub.js
Normal file
48
fusion_payroll/static/src/js/report_hub.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Component } from "@odoo/owl";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export class FusionPayrollReportHub extends Component {
|
||||
static template = "fusion_payroll.ReportHub";
|
||||
|
||||
setup() {
|
||||
this.actionService = useService("action");
|
||||
}
|
||||
|
||||
async openReport(actionXmlId) {
|
||||
try {
|
||||
await this.actionService.doAction(actionXmlId);
|
||||
} catch (error) {
|
||||
console.error("Failed to open report:", actionXmlId, error);
|
||||
// Fallback: try to open directly with context
|
||||
const reportModels = {
|
||||
'fusion_payroll.action_payroll_report_paycheque_history': 'payroll.report.paycheque.history',
|
||||
'fusion_payroll.action_payroll_report_payroll_details': 'payroll.report.payroll.details',
|
||||
'fusion_payroll.action_payroll_report_tax_liability': 'payroll.report.tax.liability',
|
||||
'fusion_payroll.action_payroll_report_tax_payments': 'payroll.report.tax.payments',
|
||||
'fusion_payroll.action_payroll_report_tax_wage_summary': 'payroll.report.tax.wage.summary',
|
||||
'fusion_payroll.action_payroll_report_summary': 'payroll.report.summary',
|
||||
'fusion_payroll.action_payroll_report_summary_employee': 'payroll.report.summary.by.employee',
|
||||
'fusion_payroll.action_payroll_report_employee_directory': 'payroll.report.employee.directory',
|
||||
'fusion_payroll.action_payroll_report_time_off': 'payroll.report.time.off',
|
||||
'fusion_payroll.action_payroll_report_total_pay': 'payroll.report.total.pay',
|
||||
'fusion_payroll.action_payroll_report_total_cost': 'payroll.report.total.cost',
|
||||
'fusion_payroll.action_payroll_report_deductions': 'payroll.report.deductions',
|
||||
'fusion_payroll.action_payroll_report_workers_comp': 'payroll.report.workers.comp',
|
||||
};
|
||||
const reportModel = reportModels[actionXmlId];
|
||||
if (reportModel) {
|
||||
await this.actionService.doAction({
|
||||
type: 'ir.actions.client',
|
||||
tag: 'fusion_payroll.payroll_report_action',
|
||||
name: 'Report',
|
||||
context: { report_model: reportModel },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("actions").add("fusion_payroll.report_hub", FusionPayrollReportHub);
|
||||
Reference in New Issue
Block a user