diff --git a/report/apps.py b/report/apps.py index 656d2d082..af4432c63 100644 --- a/report/apps.py +++ b/report/apps.py @@ -2,17 +2,18 @@ from django.apps import AppConfig class ReportConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'report' + default_auto_field = "django.db.models.BigAutoField" + name = "report" def ready(self) -> None: ready = super().ready() from django.urls import include, path - from horilla.urls import urlpatterns + from horilla.horilla_settings import APPS + from horilla.urls import urlpatterns urlpatterns.append( path("report/", include("report.urls")), ) - return ready \ No newline at end of file + return ready diff --git a/report/sidebar.py b/report/sidebar.py index 368de1da5..c65524046 100644 --- a/report/sidebar.py +++ b/report/sidebar.py @@ -1,90 +1,119 @@ -from django.utils.translation import gettext_lazy as trans -from django.urls import reverse_lazy from django.apps import apps - +from django.urls import reverse_lazy +from django.utils.translation import gettext_lazy as trans MENU = trans("Reports") IMG_SRC = "images/ui/report.svg" ACCESSIBILITY = "report.sidebar.menu_accessibility" -SUBMENUS = [ - - ] +SUBMENUS = [] # Dynamically adding submenu if the app is installed -if apps.is_installed('recruitment'): - SUBMENUS.append({ - "menu": "Recruitment", - "redirect": reverse_lazy("recruitment-report"), - "accessibility": "report.sidebar.recruitment_accessibility", - }) +if apps.is_installed("recruitment"): + SUBMENUS.append( + { + "menu": "Recruitment", + "redirect": reverse_lazy("recruitment-report"), + "accessibility": "report.sidebar.recruitment_accessibility", + } + ) -if apps.is_installed('employee'): - SUBMENUS.append({ - "menu":"Employee", - "redirect":reverse_lazy("employee-report"), - "accessibility": "report.sidebar.employee_accessibility", - }) +if apps.is_installed("employee"): + SUBMENUS.append( + { + "menu": "Employee", + "redirect": reverse_lazy("employee-report"), + "accessibility": "report.sidebar.employee_accessibility", + } + ) -if apps.is_installed('attendance'): - SUBMENUS.append({ - "menu":"Attendance", - "redirect":reverse_lazy("attendance-report"), - "accessibility": "report.sidebar.attendance_accessibility", - }) +if apps.is_installed("attendance"): + SUBMENUS.append( + { + "menu": "Attendance", + "redirect": reverse_lazy("attendance-report"), + "accessibility": "report.sidebar.attendance_accessibility", + } + ) -if apps.is_installed('leave'): - SUBMENUS.append({ - "menu":"Leave", - "redirect":reverse_lazy("leave-report"), - "accessibility": "report.sidebar.leave_accessibility", - }) +if apps.is_installed("leave"): + SUBMENUS.append( + { + "menu": "Leave", + "redirect": reverse_lazy("leave-report"), + "accessibility": "report.sidebar.leave_accessibility", + } + ) -if apps.is_installed('payroll'): - SUBMENUS.append({ - "menu":"Payroll", - "redirect":reverse_lazy("payroll-report"), - "accessibility": "report.sidebar.payroll_accessibility", - }) +if apps.is_installed("payroll"): + SUBMENUS.append( + { + "menu": "Payroll", + "redirect": reverse_lazy("payroll-report"), + "accessibility": "report.sidebar.payroll_accessibility", + } + ) -if apps.is_installed('asset'): - SUBMENUS.append({ - "menu":"Asset", - "redirect":reverse_lazy("asset-report"), - "accessibility": "report.sidebar.asset_accessibility", - }) +if apps.is_installed("asset"): + SUBMENUS.append( + { + "menu": "Asset", + "redirect": reverse_lazy("asset-report"), + "accessibility": "report.sidebar.asset_accessibility", + } + ) + +if apps.is_installed("pms"): + SUBMENUS.append( + { + "menu": "Performance", + "redirect": reverse_lazy("pms-report"), + "accessibility": "report.sidebar.pms_accessibility", + } + ) -if apps.is_installed('pms'): - SUBMENUS.append({ - "menu":"Performance", - "redirect":reverse_lazy("pms-report"), - "accessibility": "report.sidebar.pms_accessibility", - }) def menu_accessibility(request, submenu, user_perms, *args, **kwargs): - return request.user.is_superuser or request.user.has_perm("recruitment.view_recruitment") or request.user.has_perm("employee.view_employee") or request.user.has_perm("pms.view_objective") or request.user.has_perm("attendance.view_attendance") or request.user.has_perm("leave.view_leaverequest") or request.user.has_perm("payroll.view_payslip") or request.user.has_perm("asset.view_asset") - + return ( + request.user.is_superuser + or request.user.has_perm("recruitment.view_recruitment") + or request.user.has_perm("employee.view_employee") + or request.user.has_perm("pms.view_objective") + or request.user.has_perm("attendance.view_attendance") + or request.user.has_perm("leave.view_leaverequest") + or request.user.has_perm("payroll.view_payslip") + or request.user.has_perm("asset.view_asset") + ) def recruitment_accessibility(request, submenu, user_perms, *args, **kwargs): - return request.user.is_superuser or request.user.has_perm("recruitment.view_recruitment") + return request.user.is_superuser or request.user.has_perm( + "recruitment.view_recruitment" + ) + def employee_accessibility(request, submenu, user_perms, *args, **kwargs): return request.user.is_superuser or request.user.has_perm("employee.view_employee") + def attendance_accessibility(request, submenu, user_perms, *args, **kwargs): - return request.user.is_superuser or request.user.has_perm("attendance.view_attendance") + return request.user.is_superuser or request.user.has_perm( + "attendance.view_attendance" + ) + def leave_accessibility(request, submenu, user_perms, *args, **kwargs): return request.user.is_superuser or request.user.has_perm("leave.view_leaverequest") + def payroll_accessibility(request, submenu, user_perms, *args, **kwargs): return request.user.is_superuser or request.user.has_perm("payroll.view_payslip") + def asset_accessibility(request, submenu, user_perms, *args, **kwargs): return request.user.is_superuser or request.user.has_perm("asset.view_asset") + def pms_accessibility(request, submenu, user_perms, *args, **kwargs): return request.user.is_superuser or request.user.has_perm("pms.view_objective") - diff --git a/report/templates/report/asset_report.html b/report/templates/report/asset_report.html index a716f153d..4256b18fe 100644 --- a/report/templates/report/asset_report.html +++ b/report/templates/report/asset_report.html @@ -13,7 +13,7 @@

{% trans "Asset Reports" %}

- +
@@ -51,7 +51,7 @@ {{asset_filter_form.asset_tracking_id}}
- +
@@ -136,7 +136,7 @@ rendererName: "Table", // Default view as Table onRefresh: function (config) { let currentRenderer = config.rendererName; - if (currentRenderer === "Table" || currentRenderer === "Table Barchart" || + if (currentRenderer === "Table" || currentRenderer === "Table Barchart" || currentRenderer === "Heatmap" || currentRenderer === "Row Heatmap" || currentRenderer === "Col Heatmap" ) { $("#export-btn").show(); // Show button for tables } else { @@ -165,7 +165,7 @@ rendererName: "Table", // Default view as Table onRefresh: function (config) { let currentRenderer = config.rendererName; - if (currentRenderer === "Table" || currentRenderer === "Table Barchart" || + if (currentRenderer === "Table" || currentRenderer === "Table Barchart" || currentRenderer === "Heatmap" || currentRenderer === "Row Heatmap" || currentRenderer === "Col Heatmap" ) { $("#export-btn").show(); // Show button for tables } else { @@ -198,7 +198,7 @@ alert("No table found to export."); return; } - + const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet("Pivot Data"); const baseRow = 5; @@ -215,7 +215,7 @@ city: "{{ company.city|escapejs }}", zip: "{{ company.zip|escapejs }}" }; - + function getBase64FromUrl(url) { return fetch(url) .then(response => response.blob()) @@ -226,7 +226,7 @@ reader.readAsDataURL(blob); })); } - + const logoUrl = "{{ protocol }}://{{ host }}{{ company.icon.url }}"; await getBase64FromUrl(logoUrl).then((base64) => { const base64Data = base64.split(',')[1]; @@ -234,13 +234,13 @@ base64: base64Data, extension: 'png' }); - + worksheet.addImage(imageId, { tl: { col: baseCol - 1, row: currentRow - 1 }, ext: { width: 80, height: 80 } }); }); - + // Merge cells for company details text const companyTextCell = worksheet.getCell(currentRow, baseCol + 1); worksheet.mergeCells(currentRow, baseCol + 1, currentRow, baseCol + 2); @@ -258,46 +258,46 @@ wrapText: true }; worksheet.getRow(currentRow).height = 80; - + currentRow += 2; // Leave a blank row } - + // Add timestamp const timestamp = new Date().toLocaleDateString('en-GB') + ' ' + new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }); - + const downloadCell = worksheet.getCell(currentRow, baseCol); worksheet.mergeCells(currentRow, baseCol, currentRow, baseCol + 3); downloadCell.value = `Generated on: ${timestamp}`; downloadCell.alignment = { horizontal: 'left', vertical: 'middle', wrapText: true }; downloadCell.font = { size: 10, italic: true, color: { argb: 'FF666666' }, bold: true }; - + currentRow += 3; - + // Table rendering const cellMap = {}; const allRows = Array.from(table.rows); const lastRowIndex = allRows.length - 1; allRows.forEach((row, rowIndex) => { - + let colIndex = baseCol; - + Array.from(row.cells).forEach((cell) => { - + while (cellMap[`${currentRow + rowIndex}-${colIndex}`]) { colIndex++; } - + const rowspan = parseInt(cell.getAttribute("rowspan")) || 1; const colspan = parseInt(cell.getAttribute("colspan")) || 1; const cellValue = cell.textContent.trim(); - + const excelCell = worksheet.getCell(currentRow + rowIndex, colIndex); excelCell.value = cellValue; - + const isHeader = rowIndex === 0; const isLastRow = rowIndex === lastRowIndex; @@ -328,7 +328,7 @@ right: { style: 'thin' } }; excelCell.alignment = { horizontal: "center", vertical: "middle" }; - + if (rowspan > 1 || colspan > 1) { worksheet.mergeCells( currentRow + rowIndex, @@ -336,7 +336,7 @@ currentRow + rowIndex + rowspan - 1, colIndex + colspan - 1 ); - + for (let r = 0; r < rowspan; r++) { for (let c = 0; c < colspan; c++) { cellMap[`${currentRow + rowIndex + r}-${colIndex + c}`] = true; @@ -345,15 +345,15 @@ } else { cellMap[`${currentRow + rowIndex}-${colIndex}`] = true; } - + colIndex++; }); }); worksheet.getRow(currentRow + lastRowIndex).height = 25; // adjust height for Total - + worksheet.getRow(currentRow).height = 30; // adjust height for Heading - + worksheet.columns.forEach(column => { let maxLength = 2; column.eachCell({ includeEmpty: true }, cell => { @@ -362,13 +362,13 @@ }); column.width = maxLength + 3; }); - - + + const buffer = await workbook.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); - + const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = filename; diff --git a/report/templates/report/attendance_report.html b/report/templates/report/attendance_report.html index 3b36a2e62..67a84643e 100644 --- a/report/templates/report/attendance_report.html +++ b/report/templates/report/attendance_report.html @@ -11,7 +11,7 @@

{% trans "Attendance Reports" %}

- +
@@ -213,7 +213,7 @@ $.pivotUtilities.renderers, plotlyRenderers // Adding Plotly renderers ), - + // Hide specific fields from selection dropdown onRefresh: function (config) { let currentRenderer = config.rendererName; @@ -284,7 +284,7 @@ $.pivotUtilities.renderers, plotlyRenderers // Adding Plotly renderers ), - + // Hide specific fields from selection dropdown onRefresh: function (config) { let currentRenderer = config.rendererName; @@ -319,7 +319,7 @@ }, 10); } }); - + }); // When the filter form is submitted, prevent default action and load filtered data @@ -341,7 +341,7 @@ alert("No table found to export."); return; } - + const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet("Pivot Data"); const baseRow = 5; @@ -358,7 +358,7 @@ city: "{{ company.city|escapejs }}", zip: "{{ company.zip|escapejs }}" }; - + function getBase64FromUrl(url) { return fetch(url) .then(response => response.blob()) @@ -369,7 +369,7 @@ reader.readAsDataURL(blob); })); } - + const logoUrl = "{{ protocol }}://{{ host }}{{ company.icon.url }}"; await getBase64FromUrl(logoUrl).then((base64) => { const base64Data = base64.split(',')[1]; @@ -377,13 +377,13 @@ base64: base64Data, extension: 'png' }); - + worksheet.addImage(imageId, { tl: { col: baseCol - 1, row: currentRow - 1 }, ext: { width: 80, height: 80 } }); }); - + // Merge cells for company details text const companyTextCell = worksheet.getCell(currentRow, baseCol + 1); worksheet.mergeCells(currentRow, baseCol + 1, currentRow, baseCol + 2); @@ -401,46 +401,46 @@ wrapText: true }; worksheet.getRow(currentRow).height = 80; - + currentRow += 2; // Leave a blank row } - + // Add timestamp const timestamp = new Date().toLocaleDateString('en-GB') + ' ' + new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }); - + const downloadCell = worksheet.getCell(currentRow, baseCol); worksheet.mergeCells(currentRow, baseCol, currentRow, baseCol + 3); downloadCell.value = `Generated on: ${timestamp}`; downloadCell.alignment = { horizontal: 'left', vertical: 'middle', wrapText: true }; downloadCell.font = { size: 10, italic: true, color: { argb: 'FF666666' }, bold: true }; - + currentRow += 3; - + // Table rendering const cellMap = {}; const allRows = Array.from(table.rows); const lastRowIndex = allRows.length - 1; allRows.forEach((row, rowIndex) => { - + let colIndex = baseCol; - + Array.from(row.cells).forEach((cell) => { - + while (cellMap[`${currentRow + rowIndex}-${colIndex}`]) { colIndex++; } - + const rowspan = parseInt(cell.getAttribute("rowspan")) || 1; const colspan = parseInt(cell.getAttribute("colspan")) || 1; const cellValue = cell.textContent.trim(); - + const excelCell = worksheet.getCell(currentRow + rowIndex, colIndex); excelCell.value = cellValue; - + const isHeader = rowIndex === 0; const isLastRow = rowIndex === lastRowIndex; @@ -471,7 +471,7 @@ right: { style: 'thin' } }; excelCell.alignment = { horizontal: "center", vertical: "middle" }; - + if (rowspan > 1 || colspan > 1) { worksheet.mergeCells( currentRow + rowIndex, @@ -479,7 +479,7 @@ currentRow + rowIndex + rowspan - 1, colIndex + colspan - 1 ); - + for (let r = 0; r < rowspan; r++) { for (let c = 0; c < colspan; c++) { cellMap[`${currentRow + rowIndex + r}-${colIndex + c}`] = true; @@ -488,15 +488,15 @@ } else { cellMap[`${currentRow + rowIndex}-${colIndex}`] = true; } - + colIndex++; }); }); worksheet.getRow(currentRow + lastRowIndex).height = 25; // adjust height for Total - + worksheet.getRow(currentRow).height = 30; // adjust height for Heading - + worksheet.columns.forEach(column => { let maxLength = 2; column.eachCell({ includeEmpty: true }, cell => { @@ -505,13 +505,13 @@ }); column.width = maxLength + 3; }); - - + + const buffer = await workbook.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); - + const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = filename; diff --git a/report/templates/report/employee_report.html b/report/templates/report/employee_report.html index 1b1cb2d8d..f2d72db24 100644 --- a/report/templates/report/employee_report.html +++ b/report/templates/report/employee_report.html @@ -51,7 +51,7 @@ {{f.form.country}}
- +
@@ -162,7 +162,7 @@ rendererName: "Table", // Default view as Table onRefresh: function (config) { let currentRenderer = config.rendererName; - if (currentRenderer === "Table" || currentRenderer === "Table Barchart" || + if (currentRenderer === "Table" || currentRenderer === "Table Barchart" || currentRenderer === "Heatmap" || currentRenderer === "Row Heatmap" || currentRenderer === "Col Heatmap" ) { $("#export-btn").show(); // Show button for tables } else { @@ -196,7 +196,7 @@ rendererName: "Table", // Default view as Table onRefresh: function (config) { let currentRenderer = config.rendererName; - if (currentRenderer === "Table" || currentRenderer === "Table Barchart" || + if (currentRenderer === "Table" || currentRenderer === "Table Barchart" || currentRenderer === "Heatmap" || currentRenderer === "Row Heatmap" || currentRenderer === "Col Heatmap" ) { $("#export-btn").show(); // Show button for tables } else { @@ -208,7 +208,7 @@ plotlyRenderers // Adding Plotly renderers ) }); - + }); // When the filter form is submitted, prevent default action and load filtered data @@ -230,7 +230,7 @@ alert("No table found to export."); return; } - + const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet("Pivot Data"); const baseRow = 5; @@ -247,7 +247,7 @@ city: "{{ company.city|escapejs }}", zip: "{{ company.zip|escapejs }}" }; - + function getBase64FromUrl(url) { return fetch(url) .then(response => response.blob()) @@ -258,7 +258,7 @@ reader.readAsDataURL(blob); })); } - + const logoUrl = "{{ protocol }}://{{ host }}{{ company.icon.url }}"; await getBase64FromUrl(logoUrl).then((base64) => { const base64Data = base64.split(',')[1]; @@ -266,13 +266,13 @@ base64: base64Data, extension: 'png' }); - + worksheet.addImage(imageId, { tl: { col: baseCol - 1, row: currentRow - 1 }, ext: { width: 80, height: 80 } }); }); - + // Merge cells for company details text const companyTextCell = worksheet.getCell(currentRow, baseCol + 1); worksheet.mergeCells(currentRow, baseCol + 1, currentRow, baseCol + 2); @@ -290,46 +290,46 @@ wrapText: true }; worksheet.getRow(currentRow).height = 80; - + currentRow += 2; // Leave a blank row } - + // Add timestamp const timestamp = new Date().toLocaleDateString('en-GB') + ' ' + new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }); - + const downloadCell = worksheet.getCell(currentRow, baseCol); worksheet.mergeCells(currentRow, baseCol, currentRow, baseCol + 3); downloadCell.value = `Generated on: ${timestamp}`; downloadCell.alignment = { horizontal: 'left', vertical: 'middle', wrapText: true }; downloadCell.font = { size: 10, italic: true, color: { argb: 'FF666666' }, bold: true }; - + currentRow += 3; - + // Table rendering const cellMap = {}; const allRows = Array.from(table.rows); const lastRowIndex = allRows.length - 1; allRows.forEach((row, rowIndex) => { - + let colIndex = baseCol; - + Array.from(row.cells).forEach((cell) => { - + while (cellMap[`${currentRow + rowIndex}-${colIndex}`]) { colIndex++; } - + const rowspan = parseInt(cell.getAttribute("rowspan")) || 1; const colspan = parseInt(cell.getAttribute("colspan")) || 1; const cellValue = cell.textContent.trim(); - + const excelCell = worksheet.getCell(currentRow + rowIndex, colIndex); excelCell.value = cellValue; - + const isHeader = rowIndex === 0; const isLastRow = rowIndex === lastRowIndex; @@ -360,7 +360,7 @@ right: { style: 'thin' } }; excelCell.alignment = { horizontal: "center", vertical: "middle" }; - + if (rowspan > 1 || colspan > 1) { worksheet.mergeCells( currentRow + rowIndex, @@ -368,7 +368,7 @@ currentRow + rowIndex + rowspan - 1, colIndex + colspan - 1 ); - + for (let r = 0; r < rowspan; r++) { for (let c = 0; c < colspan; c++) { cellMap[`${currentRow + rowIndex + r}-${colIndex + c}`] = true; @@ -377,15 +377,15 @@ } else { cellMap[`${currentRow + rowIndex}-${colIndex}`] = true; } - + colIndex++; }); }); worksheet.getRow(currentRow + lastRowIndex).height = 25; // adjust height for Total - + worksheet.getRow(currentRow).height = 30; // adjust height for Heading - + worksheet.columns.forEach(column => { let maxLength = 2; column.eachCell({ includeEmpty: true }, cell => { @@ -394,13 +394,13 @@ }); column.width = maxLength + 3; }); - - + + const buffer = await workbook.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); - + const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = filename; diff --git a/report/templates/report/leave_report.html b/report/templates/report/leave_report.html index 5bde76d89..9589bcd91 100644 --- a/report/templates/report/leave_report.html +++ b/report/templates/report/leave_report.html @@ -12,7 +12,7 @@ {% trans "Leave Reports" %} - +
@@ -78,7 +78,7 @@ {{form.from_date}}
- +
@@ -292,17 +292,17 @@ containerId = "pivot-availableleave"; rowsConfig = ["Name","Department", "Leave Type","Assigned Date","Total Leave Days","Available Days","Carryforward Days"] } - - + + // Show relevant container $("#" + containerId).show(); // Fetch data dynamically based on model $.getJSON(url, function (data) { - + // Add Plotly renderers correctly let plotlyRenderers = $.pivotUtilities.plotly_renderers; - + // Initialize pivot table with Plotly enabled $("#" + containerId).pivotUI(data, { rows: rowsConfig, @@ -331,12 +331,12 @@ window.loadFilteredPivotData =function loadFilteredPivotData() { const selectedModel = $("#model-select").val(); const formData = $("#filterForm").serialize(); - + $(".pivot-wrapper").hide(); - + let containerId = ""; let rowsConfig = []; - + if (selectedModel === "leave_request"){ containerId = "pivot-leave"; rowsConfig = ["Name","Department","Shift", "Leave Type","Requested Days","Start Date","End Date","Status"] @@ -344,13 +344,13 @@ containerId = "pivot-availableleave"; rowsConfig = ["Name","Department", "Leave Type","Assigned Date","Total Leave Days","Available Days","Carryforward Days"] } - + $("#" + containerId).show(); - + $.getJSON(`leave-pivot?model=${selectedModel}&${formData}`, function (data) { const plotlyRenderers = $.pivotUtilities.plotly_renderers; - + $("#" + containerId).pivotUI(data, { rows: rowsConfig, cols: [], @@ -364,21 +364,21 @@ } else { $("#export-btn").hide(); } - + } }); }); } - + // Initial load with all models loadPivotData("leave_request"); - + // Model selection change event $("#model-select").on("change", function () { let selectedModel = $(this).val(); loadPivotData(selectedModel); // Reload pivot data with selected model }); - + // Export to Excel on button click $("#export-btn").on("click", function () { let visiblePivot = $(".pivot-wrapper:visible .pvtTable").closest(".pivot-wrapper"); @@ -397,14 +397,14 @@ alert("No table found to export."); return; } - + const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet("Pivot Data"); const baseRow = 5; const baseCol = 5; - + let currentRow = baseRow; - + // Add company details first (if not 'all') if ('{{company}}' !== 'all') { const companyDetails = { @@ -415,7 +415,7 @@ city: "{{ company.city|escapejs }}", zip: "{{ company.zip|escapejs }}" }; - + function getBase64FromUrl(url) { return fetch(url) .then(response => response.blob()) @@ -426,7 +426,7 @@ reader.readAsDataURL(blob); })); } - + const logoUrl = "{{ protocol }}://{{ host }}{{ company.icon.url }}"; await getBase64FromUrl(logoUrl).then((base64) => { const base64Data = base64.split(',')[1]; @@ -434,13 +434,13 @@ base64: base64Data, extension: 'png' }); - + worksheet.addImage(imageId, { tl: { col: baseCol - 1, row: currentRow - 1 }, ext: { width: 80, height: 80 } }); }); - + // Merge cells for company details text const companyTextCell = worksheet.getCell(currentRow, baseCol + 1); worksheet.mergeCells(currentRow, baseCol + 1, currentRow, baseCol + 2); @@ -458,51 +458,51 @@ wrapText: true }; worksheet.getRow(currentRow).height = 80; - + currentRow += 2; // Leave a blank row } - + // Add timestamp const timestamp = new Date().toLocaleDateString('en-GB') + ' ' + new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }); - + const downloadCell = worksheet.getCell(currentRow, baseCol); worksheet.mergeCells(currentRow, baseCol, currentRow, baseCol + 3); downloadCell.value = `Generated on: ${timestamp}`; downloadCell.alignment = { horizontal: 'left', vertical: 'middle', wrapText: true }; downloadCell.font = { size: 10, italic: true, color: { argb: 'FF666666' }, bold: true }; - + currentRow += 3; // Leave some rows before the table - + // ------------------------ // Render pivot table // ------------------------ const cellMap = {}; const allRows = Array.from(table.rows); const lastRowIndex = allRows.length - 1; - + allRows.forEach((row, rowIndex) => { - + let colIndex = baseCol; - + Array.from(row.cells).forEach((cell) => { - + while (cellMap[`${currentRow + rowIndex}-${colIndex}`]) { colIndex++; } - + const rowspan = parseInt(cell.getAttribute("rowspan")) || 1; const colspan = parseInt(cell.getAttribute("colspan")) || 1; const cellValue = cell.textContent.trim(); - + const excelCell = worksheet.getCell(currentRow + rowIndex, colIndex); excelCell.value = cellValue; - + const isHeader = rowIndex === 0; const isLastRow = rowIndex === lastRowIndex; - + excelCell.font = { bold: isHeader || isLastRow, size: isHeader ? 12 : 11, @@ -512,7 +512,7 @@ 'FF000000' } }; - + excelCell.fill = { type: 'pattern', pattern: 'solid', @@ -530,7 +530,7 @@ right: { style: 'thin' } }; excelCell.alignment = { horizontal: "center", vertical: "middle" }; - + // Merge if (rowspan > 1 || colspan > 1) { worksheet.mergeCells( @@ -539,7 +539,7 @@ currentRow + rowIndex + rowspan - 1, colIndex + colspan - 1 ); - + for (let r = 0; r < rowspan; r++) { for (let c = 0; c < colspan; c++) { cellMap[`${currentRow + rowIndex + r}-${colIndex + c}`] = true; @@ -548,15 +548,15 @@ } else { cellMap[`${currentRow + rowIndex}-${colIndex}`] = true; } - + colIndex++; }); }); - + worksheet.getRow(currentRow + lastRowIndex).height = 25; // adjust height for Total - + worksheet.getRow(currentRow).height = 30; // adjust height for Heading - + // Auto-adjust column widths worksheet.columns.forEach(column => { let maxLength = 2; @@ -566,13 +566,13 @@ }); column.width = maxLength + 3; }); - + // Save const buffer = await workbook.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); - + const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = filename; @@ -580,7 +580,7 @@ } }); - + diff --git a/report/templates/report/payroll_report.html b/report/templates/report/payroll_report.html index 6500e3c2b..8917711b0 100644 --- a/report/templates/report/payroll_report.html +++ b/report/templates/report/payroll_report.html @@ -62,8 +62,8 @@
- - + +
@@ -208,7 +208,7 @@
- + @@ -242,7 +242,7 @@ +
@@ -260,11 +260,11 @@ // Hide all containers first $(".pivot-wrapper").hide(); - + // Determine current container and row config let containerId = ""; let rowsConfig = []; - + if (model === "payslip") { containerId = "pivot-payslip"; rowsConfig = ["Employee","Basic Salary","Gross Pay","Net Pay","Status"]; @@ -272,7 +272,7 @@ containerId = "pivot-allowance"; rowsConfig = ["Employee","Allowance & Deduction","Allowance & Deduction Title","Allowance & Deduction Amount"]; } - + // Show relevant container $("#" + containerId).show(); @@ -280,20 +280,20 @@ $.getJSON(url, function (data) { // Add Plotly renderers correctly let plotlyRenderers = $.pivotUtilities.plotly_renderers; - + // Initialize pivot table with Plotly enabled $("#" + containerId).pivotUI(data, { - + rows: rowsConfig, cols: [], // Default columns aggregatorName: "Count", // Default aggregator rendererName: "Table", // Default view as Table - + renderers: $.extend( $.pivotUtilities.renderers, plotlyRenderers // Adding Plotly renderers ), - + onRefresh: function (config) { let currentRenderer = config.rendererName; if ( @@ -307,10 +307,10 @@ } else { $("#export-btn").hide(); // Hide button for charts } - + // ✅ Hide fields from the dropdown but keep them visible in the table let hiddenFields = ["Allowance Amount", "Deduction Amount"]; - + setTimeout(function () { $(".pvtAttrDropdown option").each(function () { if (hiddenFields.includes($(this).text())) { @@ -324,12 +324,12 @@ window.loadFilteredPivotData =function loadFilteredPivotData() { const selectedModel = $("#model-select").val(); const formData = $("#filterForm").serialize(); - + $(".pivot-wrapper").hide(); - + let containerId = ""; let rowsConfig = []; - + if (model === "payslip") { containerId = "pivot-payslip"; rowsConfig = ["Employee","Basic Salary","Gross Pay","Net Pay","Status"]; @@ -337,13 +337,13 @@ containerId = "pivot-allowance"; rowsConfig = ["Employee","Allowance & Deduction","Allowance & Deduction Title","Allowance & Deduction Amount"]; } - + $("#" + containerId).show(); - + $.getJSON(`payroll-pivot?model=${selectedModel}&${formData}`, function (data) { - + const plotlyRenderers = $.pivotUtilities.plotly_renderers; - + $("#" + containerId).pivotUI(data, { rows: rowsConfig, cols: [], @@ -357,14 +357,14 @@ } else { $("#export-btn").hide(); } - + } }); }); } - - + + // Export to Excel on button click $("#export-btn").on("click", function () { let visiblePivot = $(".pivot-wrapper:visible .pvtTable").closest(".pivot-wrapper"); @@ -382,14 +382,14 @@ alert("No table found to export."); return; } - + const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet("Pivot Data"); const baseRow = 5; const baseCol = 5; - + let currentRow = baseRow; - + // Add company details first (if not 'all') if ('{{company}}' !== 'all') { const companyDetails = { @@ -400,7 +400,7 @@ city: "{{ company.city|escapejs }}", zip: "{{ company.zip|escapejs }}" }; - + function getBase64FromUrl(url) { return fetch(url) .then(response => response.blob()) @@ -411,7 +411,7 @@ reader.readAsDataURL(blob); })); } - + const logoUrl = "{{ protocol }}://{{ host }}{{ company.icon.url }}"; await getBase64FromUrl(logoUrl).then((base64) => { const base64Data = base64.split(',')[1]; @@ -419,13 +419,13 @@ base64: base64Data, extension: 'png' }); - + worksheet.addImage(imageId, { tl: { col: baseCol - 1, row: currentRow - 1 }, ext: { width: 80, height: 80 } }); }); - + // Merge cells for company details text const companyTextCell = worksheet.getCell(currentRow, baseCol + 1); worksheet.mergeCells(currentRow, baseCol + 1, currentRow, baseCol + 2); @@ -443,51 +443,51 @@ wrapText: true }; worksheet.getRow(currentRow).height = 80; - + currentRow += 2; // Leave a blank row } - + // Add timestamp const timestamp = new Date().toLocaleDateString('en-GB') + ' ' + new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }); - + const downloadCell = worksheet.getCell(currentRow, baseCol); worksheet.mergeCells(currentRow, baseCol, currentRow, baseCol + 3); downloadCell.value = `Generated on: ${timestamp}`; downloadCell.alignment = { horizontal: 'left', vertical: 'middle', wrapText: true }; downloadCell.font = { size: 10, italic: true, color: { argb: 'FF666666' }, bold: true }; - + currentRow += 3; // Leave some rows before the table - + // ------------------------ // Render pivot table // ------------------------ const cellMap = {}; const allRows = Array.from(table.rows); const lastRowIndex = allRows.length - 1; - + allRows.forEach((row, rowIndex) => { - + let colIndex = baseCol; - + Array.from(row.cells).forEach((cell) => { - + while (cellMap[`${currentRow + rowIndex}-${colIndex}`]) { colIndex++; } - + const rowspan = parseInt(cell.getAttribute("rowspan")) || 1; const colspan = parseInt(cell.getAttribute("colspan")) || 1; const cellValue = cell.textContent.trim(); - + const excelCell = worksheet.getCell(currentRow + rowIndex, colIndex); excelCell.value = cellValue; - + const isHeader = rowIndex === 0; const isLastRow = rowIndex === lastRowIndex; - + excelCell.font = { bold: isHeader || isLastRow, size: isHeader ? 12 : 11, @@ -497,7 +497,7 @@ 'FF000000' } }; - + excelCell.fill = { type: 'pattern', pattern: 'solid', @@ -515,7 +515,7 @@ right: { style: 'thin' } }; excelCell.alignment = { horizontal: "center", vertical: "middle" }; - + // Merge if (rowspan > 1 || colspan > 1) { worksheet.mergeCells( @@ -524,7 +524,7 @@ currentRow + rowIndex + rowspan - 1, colIndex + colspan - 1 ); - + for (let r = 0; r < rowspan; r++) { for (let c = 0; c < colspan; c++) { cellMap[`${currentRow + rowIndex + r}-${colIndex + c}`] = true; @@ -533,15 +533,15 @@ } else { cellMap[`${currentRow + rowIndex}-${colIndex}`] = true; } - + colIndex++; }); }); - + worksheet.getRow(currentRow + lastRowIndex).height = 25; // adjust height for Total - + worksheet.getRow(currentRow).height = 30; // adjust height for Heading - + // Auto-adjust column widths worksheet.columns.forEach(column => { let maxLength = 2; @@ -551,13 +551,13 @@ }); column.width = maxLength + 3; }); - + // Save const buffer = await workbook.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); - + const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = filename; @@ -574,8 +574,8 @@ loadPivotData(selectedModel); // Reload pivot data with selected model }); }); - - + + diff --git a/report/templates/report/pms_report.html b/report/templates/report/pms_report.html index 7c3a98611..6f77c6f44 100644 --- a/report/templates/report/pms_report.html +++ b/report/templates/report/pms_report.html @@ -196,7 +196,7 @@ - +
@@ -216,11 +216,11 @@ function loadPivotData(model) { // Hide all containers first $(".pivot-wrapper").hide(); - + // Determine current container and row config let containerId = ""; let rowsConfig = []; - + if (model === "feedback") { containerId = "pivot-feedback"; rowsConfig = ["Title","Employee","Manager","Answerable Employees","Answered Employees","Questions","Answer"]; @@ -231,14 +231,14 @@ containerId = "pivot-employeeobjective"; rowsConfig = ["Employee", "Objective","Employee Keyresult","Keyresult Target Value","Keyresult Current Value"]; } - + // Show relevant container $("#" + containerId).show(); - + // Fetch and render data in its own container $.getJSON(`pms-pivot?model=${model}`, function (data) { let plotlyRenderers = $.pivotUtilities.plotly_renderers; - + $("#" + containerId).pivotUI(data, { rows: rowsConfig, cols: [], @@ -252,9 +252,9 @@ } else { $("#export-btn").hide(); } - + $(".pvtTotal, .pvtTotalLabel, .pvtGrandTotal, .pvtAggregator").hide(); - + setTimeout(() => { $(".pvtAttrDropdown option").each(function () { if (hiddenFields.includes($(this).text())) { @@ -271,12 +271,12 @@ window.loadFilteredPivotData =function loadFilteredPivotData() { const selectedModel = $("#model-select").val(); const formData = $("#filterForm").serialize(); - + $(".pivot-wrapper").hide(); - + let containerId = ""; let rowsConfig = []; - + if (selectedModel === "feedback") { containerId = "pivot-feedback"; rowsConfig = ["Title","Employee", "Manager", "Answerable Employees", "Answered Employees", "Questions", "Answer"]; @@ -287,15 +287,15 @@ containerId = "pivot-employeeobjective"; rowsConfig = ["Employee", "Objective", "Employee Keyresult", "Keyresult Target Value", "Keyresult Current Value"]; } - + $("#" + containerId).show(); - + $.getJSON(`pms-pivot?model=${selectedModel}&${formData}`, function (data) { console.log("Filtered data:", data); const plotlyRenderers = $.pivotUtilities.plotly_renderers; - + $("#" + containerId).pivotUI(data, { rows: rowsConfig, cols: [], @@ -309,24 +309,24 @@ } else { $("#export-btn").hide(); } - + $(".pvtTotal, .pvtTotalLabel, .pvtGrandTotal, .pvtAggregator").hide(); } }); }); } - - + + // Listen to dropdown $("#model-select").on("change", function () { let selectedModel = $(this).val(); loadPivotData(selectedModel); }); - + // Initial load loadPivotData("objective"); - - + + // Export to Excel on button click $("#export-btn").on("click", function () { let visiblePivot = $(".pivot-wrapper:visible .pvtTable").closest(".pivot-wrapper"); @@ -343,14 +343,14 @@ alert("No table found to export."); return; } - + const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet("Pivot Data"); const baseRow = 5; const baseCol = 5; - + let currentRow = baseRow; - + // Add company details first (if not 'all') if ('{{company}}' !== 'all') { const companyDetails = { @@ -361,7 +361,7 @@ city: "{{ company.city|escapejs }}", zip: "{{ company.zip|escapejs }}" }; - + function getBase64FromUrl(url) { return fetch(url) .then(response => response.blob()) @@ -372,7 +372,7 @@ reader.readAsDataURL(blob); })); } - + const logoUrl = "{{ protocol }}://{{ host }}{{ company.icon.url }}"; await getBase64FromUrl(logoUrl).then((base64) => { const base64Data = base64.split(',')[1]; @@ -380,13 +380,13 @@ base64: base64Data, extension: 'png' }); - + worksheet.addImage(imageId, { tl: { col: baseCol - 1, row: currentRow - 1 }, ext: { width: 80, height: 80 } }); }); - + // Merge cells for company details text const companyTextCell = worksheet.getCell(currentRow, baseCol + 1); worksheet.mergeCells(currentRow, baseCol + 1, currentRow, baseCol + 2); @@ -404,24 +404,24 @@ wrapText: true }; worksheet.getRow(currentRow).height = 80; - + currentRow += 2; // Leave a blank row } - + // Add timestamp const timestamp = new Date().toLocaleDateString('en-GB') + ' ' + new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }); - + const downloadCell = worksheet.getCell(currentRow, baseCol); worksheet.mergeCells(currentRow, baseCol, currentRow, baseCol + 3); downloadCell.value = `Generated on: ${timestamp}`; downloadCell.alignment = { horizontal: 'left', vertical: 'middle', wrapText: true }; downloadCell.font = { size: 10, italic: true, color: { argb: 'FF666666' }, bold: true }; - + currentRow += 3; // Leave some rows before the table - + // ------------------------ // Render pivot table // ------------------------ @@ -431,9 +431,9 @@ row.classList.contains("pvtTotal") || row.classList.contains("pvtGrandTotal") ) return; - + let colIndex = baseCol; - + Array.from(row.cells).forEach((cell) => { if ( cell.classList.contains("pvtTotal") || @@ -441,18 +441,18 @@ cell.classList.contains("pvtAggregator") || cell.classList.contains("pvtGrandTotal") ) return; - + while (cellMap[`${currentRow + rowIndex}-${colIndex}`]) { colIndex++; } - + const rowspan = parseInt(cell.getAttribute("rowspan")) || 1; const colspan = parseInt(cell.getAttribute("colspan")) || 1; const cellValue = cell.textContent.trim(); - + const excelCell = worksheet.getCell(currentRow + rowIndex, colIndex); excelCell.value = cellValue; - + excelCell.font = { bold: rowIndex === 0, size: rowIndex === 0 ? 12 : 11, @@ -470,7 +470,7 @@ right: { style: 'thin' } }; excelCell.alignment = { horizontal: "center", vertical: "middle" }; - + // Merge if (rowspan > 1 || colspan > 1) { worksheet.mergeCells( @@ -479,7 +479,7 @@ currentRow + rowIndex + rowspan - 1, colIndex + colspan - 1 ); - + for (let r = 0; r < rowspan; r++) { for (let c = 0; c < colspan; c++) { cellMap[`${currentRow + rowIndex + r}-${colIndex + c}`] = true; @@ -488,14 +488,14 @@ } else { cellMap[`${currentRow + rowIndex}-${colIndex}`] = true; } - + colIndex++; }); }); - + // Header row height worksheet.getRow(currentRow).height = 30; - + // Auto-adjust column widths worksheet.columns.forEach(column => { let maxLength = 5; @@ -505,21 +505,21 @@ }); column.width = maxLength + 0; }); - + // Save const buffer = await workbook.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); - + const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = filename; link.click(); } - + }); - + diff --git a/report/templates/report/recruitment_report.html b/report/templates/report/recruitment_report.html index f37dde34f..e74212e29 100644 --- a/report/templates/report/recruitment_report.html +++ b/report/templates/report/recruitment_report.html @@ -60,7 +60,7 @@ {{ f.form.job_position_id }}
- +
{{ f.form.hired }} @@ -77,12 +77,12 @@ {{ f.form.email }}
- +
{{ f.form.gender }}
- +
- +
@@ -265,7 +265,7 @@ // Hide all containers first $(".pivot-wrapper").hide(); - + // Determine current container and row config let containerId = ""; let rowsConfig = []; @@ -280,14 +280,14 @@ containerId = "pivot-onboarding"; rowsConfig = ["Recruitment","Candidates","Stage","Stage Manager","Task","Task Manager","Company"]; } - + // Show relevant container $("#" + containerId).show(); $.getJSON(url, function (data) { // Add Plotly renderers correctly let plotlyRenderers = $.pivotUtilities.plotly_renderers; - + // Initialize pivot table with Plotly enabled $("#" + containerId).pivotUI(data, { rows: rowsConfig, @@ -296,7 +296,7 @@ rendererName: "Table", // Default view as Table onRefresh: function (config) { let currentRenderer = config.rendererName; - if (currentRenderer === "Table" || currentRenderer === "Table Barchart" || + if (currentRenderer === "Table" || currentRenderer === "Table Barchart" || currentRenderer === "Heatmap" || currentRenderer === "Row Heatmap" || currentRenderer === "Col Heatmap" ) { $("#export-btn").show(); // Show button for tables } else { @@ -329,12 +329,12 @@ window.loadFilteredPivotData =function loadFilteredPivotData() { const selectedModel = $("#model-select").val(); const formData = $("#filterForm").serialize(); - + $(".pivot-wrapper").hide(); - + let containerId = ""; let rowsConfig = []; - + if (selectedModel === "candidate") { containerId = "pivot-candidate"; rowsConfig = ["Recruitment","Job Position","Department","Candidate","Gender","Email"]; @@ -345,13 +345,13 @@ containerId = "pivot-onboarding"; rowsConfig = ["Recruitment","Candidates","Stage","Stage Manager","Task","Task Manager","Company"]; } - + $("#" + containerId).show(); - + $.getJSON(`recruitment-pivot?model=${selectedModel}&${formData}`, function (data) { const plotlyRenderers = $.pivotUtilities.plotly_renderers; - + $("#" + containerId).pivotUI(data, { rows: rowsConfig, cols: [], @@ -365,7 +365,7 @@ } else { $("#export-btn").hide(); } - + } }); }); @@ -374,7 +374,7 @@ // Initial load with all models loadPivotData("candidate"); - + // Model selection change event $("#model-select").on("change", function () { let selectedModel = $(this).val(); @@ -397,14 +397,14 @@ alert("No table found to export."); return; } - + const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet("Pivot Data"); const baseRow = 5; const baseCol = 5; - + let currentRow = baseRow; - + // Add company details first (if not 'all') if ('{{company}}' !== 'all') { const companyDetails = { @@ -415,7 +415,7 @@ city: "{{ company.city|escapejs }}", zip: "{{ company.zip|escapejs }}" }; - + function getBase64FromUrl(url) { return fetch(url) .then(response => response.blob()) @@ -426,7 +426,7 @@ reader.readAsDataURL(blob); })); } - + const logoUrl = "{{ protocol }}://{{ host }}{{ company.icon.url }}"; await getBase64FromUrl(logoUrl).then((base64) => { const base64Data = base64.split(',')[1]; @@ -434,13 +434,13 @@ base64: base64Data, extension: 'png' }); - + worksheet.addImage(imageId, { tl: { col: baseCol - 1, row: currentRow - 1 }, ext: { width: 80, height: 80 } }); }); - + // Merge cells for company details text const companyTextCell = worksheet.getCell(currentRow, baseCol + 1); worksheet.mergeCells(currentRow, baseCol + 1, currentRow, baseCol + 2); @@ -458,35 +458,35 @@ wrapText: true }; worksheet.getRow(currentRow).height = 80; - + currentRow += 2; // Leave a blank row } - + // Add timestamp const timestamp = new Date().toLocaleDateString('en-GB') + ' ' + new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }); - + const downloadCell = worksheet.getCell(currentRow, baseCol); worksheet.mergeCells(currentRow, baseCol, currentRow, baseCol + 3); downloadCell.value = `Generated on: ${timestamp}`; downloadCell.alignment = { horizontal: 'left', vertical: 'middle', wrapText: true }; downloadCell.font = { size: 10, italic: true, color: { argb: 'FF666666' }, bold: true }; - + currentRow += 3; // Leave some rows before the table - + // ------------------------ // Render pivot table // ------------------------ const cellMap = {}; const allRows = Array.from(table.rows); const lastRowIndex = allRows.length - 1; - + allRows.forEach((row, rowIndex) => { - + let colIndex = baseCol; - + Array.from(row.cells).forEach((cell) => { if ( @@ -495,21 +495,21 @@ cell.classList.contains("pvtAggregator") || cell.classList.contains("pvtGrandTotal") ) return; - + while (cellMap[`${currentRow + rowIndex}-${colIndex}`]) { colIndex++; } - + const rowspan = parseInt(cell.getAttribute("rowspan")) || 1; const colspan = parseInt(cell.getAttribute("colspan")) || 1; const cellValue = cell.textContent.trim(); - + const excelCell = worksheet.getCell(currentRow + rowIndex, colIndex); excelCell.value = cellValue; - + const isHeader = rowIndex === 0; const isLastRow = rowIndex === lastRowIndex; - + excelCell.font = { bold: isHeader || isLastRow, size: isHeader ? 12 : 11, @@ -519,7 +519,7 @@ 'FF000000' } }; - + excelCell.fill = { type: 'pattern', pattern: 'solid', @@ -537,7 +537,7 @@ right: { style: 'thin' } }; excelCell.alignment = { horizontal: "center", vertical: "middle" }; - + // Merge if (rowspan > 1 || colspan > 1) { worksheet.mergeCells( @@ -546,7 +546,7 @@ currentRow + rowIndex + rowspan - 1, colIndex + colspan - 1 ); - + for (let r = 0; r < rowspan; r++) { for (let c = 0; c < colspan; c++) { cellMap[`${currentRow + rowIndex + r}-${colIndex + c}`] = true; @@ -555,15 +555,15 @@ } else { cellMap[`${currentRow + rowIndex}-${colIndex}`] = true; } - + colIndex++; }); }); - + worksheet.getRow(currentRow + lastRowIndex).height = 25; // adjust height for Total - + worksheet.getRow(currentRow).height = 30; // adjust height for Heading - + // Auto-adjust column widths worksheet.columns.forEach(column => { let maxLength = 2; @@ -573,13 +573,13 @@ }); column.width = maxLength + 3; }); - + // Save const buffer = await workbook.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); - + const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = filename; @@ -587,7 +587,7 @@ } }); - + diff --git a/report/urls.py b/report/urls.py index 8296004f0..f25c872e3 100644 --- a/report/urls.py +++ b/report/urls.py @@ -1,70 +1,84 @@ - -from django.urls import path -from report.views import asset_report, employee_report,attendance_report,leave_report,payroll_report, pms_report,recruitment_report from django.apps import apps +from django.urls import path - - +from report.views import ( + asset_report, + attendance_report, + employee_report, + leave_report, + payroll_report, + pms_report, + recruitment_report, +) urlpatterns = [ - - path("employee-report",employee_report.employee_report,name='employee-report'), - path("employee-pivot",employee_report.employee_pivot,name='employee-pivot'), - + path("employee-report", employee_report.employee_report, name="employee-report"), + path("employee-pivot", employee_report.employee_pivot, name="employee-pivot"), ] if apps.is_installed("recruitment"): urlpatterns.extend( [ - path("recruitment-report",recruitment_report.recruitment_report,name='recruitment-report'), - path("recruitment-pivot",recruitment_report.recruitment_pivot,name='recruitment-pivot'), - + path( + "recruitment-report", + recruitment_report.recruitment_report, + name="recruitment-report", + ), + path( + "recruitment-pivot", + recruitment_report.recruitment_pivot, + name="recruitment-pivot", + ), ] ) if apps.is_installed("attendance"): urlpatterns.extend( [ - path("attendance-report",attendance_report.attendance_report,name='attendance-report'), - path("attendance-pivot",attendance_report.attendance_pivot,name='attendance-pivot'), - + path( + "attendance-report", + attendance_report.attendance_report, + name="attendance-report", + ), + path( + "attendance-pivot", + attendance_report.attendance_pivot, + name="attendance-pivot", + ), ] ) if apps.is_installed("leave"): urlpatterns.extend( [ - path("leave-report",leave_report.leave_report, name="leave-report"), - path("leave-pivot",leave_report.leave_pivot,name='leave-pivot'), - + path("leave-report", leave_report.leave_report, name="leave-report"), + path("leave-pivot", leave_report.leave_pivot, name="leave-pivot"), ] ) if apps.is_installed("payroll"): urlpatterns.extend( [ - path("payroll-report",payroll_report.payroll_report, name="payroll-report"), - path("payroll-pivot",payroll_report.payroll_pivot,name='payroll-pivot'), - + path( + "payroll-report", payroll_report.payroll_report, name="payroll-report" + ), + path("payroll-pivot", payroll_report.payroll_pivot, name="payroll-pivot"), ] ) if apps.is_installed("asset"): urlpatterns.extend( [ - path("asset-report",asset_report.asset_report, name="asset-report"), - path("asset-pivot",asset_report.asset_pivot,name='asset-pivot'), - + path("asset-report", asset_report.asset_report, name="asset-report"), + path("asset-pivot", asset_report.asset_pivot, name="asset-pivot"), ] ) if apps.is_installed("pms"): urlpatterns.extend( [ - path("pms-report",pms_report.pms_report, name="pms-report"), - path("pms-pivot",pms_report.pms_pivot,name='pms-pivot'), - + path("pms-report", pms_report.pms_report, name="pms-report"), + path("pms-pivot", pms_report.pms_pivot, name="pms-pivot"), ] ) - diff --git a/report/views/asset_report.py b/report/views/asset_report.py index ed861d580..fbec4c5ae 100644 --- a/report/views/asset_report.py +++ b/report/views/asset_report.py @@ -1,25 +1,32 @@ +from django.apps import apps from django.http import JsonResponse from django.shortcuts import render -from django.apps import apps if apps.is_installed("asset"): from asset.filters import AssetFilter + from asset.models import Asset from base.models import Company from horilla_views.cbv_methods import login_required, permission_required - from asset.models import Asset @login_required @permission_required(perm="asset.view_asset") def asset_report(request): - company = 'all' + company = "all" selected_company = request.session.get("selected_company") - if selected_company != 'all': + if selected_company != "all": company = Company.objects.filter(id=selected_company).first() asset_filter_form = AssetFilter() - return render(request, "report/asset_report.html",{"company":company,"asset_filter_form": asset_filter_form.form,}) + return render( + request, + "report/asset_report.html", + { + "company": company, + "asset_filter_form": asset_filter_form.form, + }, + ) @login_required @permission_required(perm="asset.view_asset") @@ -27,52 +34,121 @@ if apps.is_installed("asset"): qs = Asset.objects.all() if asset_name := request.GET.get("asset_name"): - qs = qs.filter(asset_name = asset_name) + qs = qs.filter(asset_name=asset_name) if asset_tracking_id := request.GET.get("asset_tracking_id"): - qs = qs.filter(asset_tracking_id = asset_tracking_id) + qs = qs.filter(asset_tracking_id=asset_tracking_id) if asset_purchase_cost := request.GET.get("asset_purchase_cost"): - qs = qs.filter(asset_purchase_cost = asset_purchase_cost) + qs = qs.filter(asset_purchase_cost=asset_purchase_cost) if asset_lot_number_id := request.GET.get("asset_lot_number_id"): - qs = qs.filter(asset_lot_number_id = asset_lot_number_id) + qs = qs.filter(asset_lot_number_id=asset_lot_number_id) if asset_category_id := request.GET.get("asset_category_id"): - qs = qs.filter(asset_category_id = asset_category_id) + qs = qs.filter(asset_category_id=asset_category_id) if asset_status := request.GET.get("asset_status"): - qs = qs.filter(asset_status = asset_status) + qs = qs.filter(asset_status=asset_status) if asset_purchase_date := request.GET.get("asset_purchase_date"): - qs = qs.filter(asset_purchase_date = asset_purchase_date) + qs = qs.filter(asset_purchase_date=asset_purchase_date) - data = list(qs.values( - "asset_name","asset_purchase_date","asset_tracking_id", - "asset_purchase_cost","asset_status","asset_category_id__asset_category_name","asset_lot_number_id__lot_number", - "expiry_date","assetassignment__assigned_by_employee_id__employee_work_info__department_id__department", - "assetassignment__assigned_by_employee_id__employee_work_info__job_position_id__job_position", - "assetassignment__assigned_by_employee_id__employee_work_info__job_role_id__job_role", - "assetassignment__assigned_by_employee_id__email","assetassignment__assigned_by_employee_id__phone", - "assetassignment__assigned_by_employee_id__gender","assetassignment__assigned_by_employee_id__employee_first_name", - "assetassignment__assigned_by_employee_id__employee_last_name","assetassignment__assigned_date", - "assetassignment__return_date","assetassignment__return_status", - - )) + data = list( + qs.values( + "asset_name", + "asset_purchase_date", + "asset_tracking_id", + "asset_purchase_cost", + "asset_status", + "asset_category_id__asset_category_name", + "asset_lot_number_id__lot_number", + "expiry_date", + "assetassignment__assigned_by_employee_id__employee_work_info__department_id__department", + "assetassignment__assigned_by_employee_id__employee_work_info__job_position_id__job_position", + "assetassignment__assigned_by_employee_id__employee_work_info__job_role_id__job_role", + "assetassignment__assigned_by_employee_id__email", + "assetassignment__assigned_by_employee_id__phone", + "assetassignment__assigned_by_employee_id__gender", + "assetassignment__assigned_by_employee_id__employee_first_name", + "assetassignment__assigned_by_employee_id__employee_last_name", + "assetassignment__assigned_date", + "assetassignment__return_date", + "assetassignment__return_status", + ) + ) data_list = [ { - "Asset Name" : item["asset_name"], - "Asset User": f"{item['assetassignment__assigned_by_employee_id__employee_first_name']} {item['assetassignment__assigned_by_employee_id__employee_last_name']}" if item["assetassignment__assigned_by_employee_id__employee_first_name"] or item["assetassignment__assigned_by_employee_id__employee_last_name"] else "-", - "Email":item["assetassignment__assigned_by_employee_id__email"] if item["assetassignment__assigned_by_employee_id__email"] else "-", - "Phone":item["assetassignment__assigned_by_employee_id__phone"] if item["assetassignment__assigned_by_employee_id__phone"] else "-", - "Gender":item["assetassignment__assigned_by_employee_id__gender"] if item["assetassignment__assigned_by_employee_id__gender"] else "-", - "Department":item["assetassignment__assigned_by_employee_id__employee_work_info__department_id__department"] if item["assetassignment__assigned_by_employee_id__employee_work_info__department_id__department"] else "-", - "Job Position":item["assetassignment__assigned_by_employee_id__employee_work_info__job_position_id__job_position"] if item["assetassignment__assigned_by_employee_id__employee_work_info__job_position_id__job_position"] else "-", - "Job Role":item["assetassignment__assigned_by_employee_id__employee_work_info__job_role_id__job_role"] if item["assetassignment__assigned_by_employee_id__employee_work_info__job_role_id__job_role"] else "-", - "Asset Purchce Date":item["asset_purchase_date"], - "Asset Cost":item["asset_purchase_cost"], - "Status":item["asset_status"], - "Assigned Date":item["assetassignment__assigned_date"] if item["assetassignment__assigned_date"] else "-", - "Return Date":item["assetassignment__return_date"] if item["assetassignment__return_date"] else "-", - "Return Condition":item["assetassignment__return_status"] if item["assetassignment__return_status"] else "-", - "Category":item["asset_category_id__asset_category_name"], - "Batch Number":item["asset_lot_number_id__lot_number"], - "Tracking ID":item["asset_tracking_id"], - "Expiry Date":item["expiry_date"] if item["expiry_date"] else "-", - }for item in data + "Asset Name": item["asset_name"], + "Asset User": ( + f"{item['assetassignment__assigned_by_employee_id__employee_first_name']} {item['assetassignment__assigned_by_employee_id__employee_last_name']}" + if item[ + "assetassignment__assigned_by_employee_id__employee_first_name" + ] + or item[ + "assetassignment__assigned_by_employee_id__employee_last_name" + ] + else "-" + ), + "Email": ( + item["assetassignment__assigned_by_employee_id__email"] + if item["assetassignment__assigned_by_employee_id__email"] + else "-" + ), + "Phone": ( + item["assetassignment__assigned_by_employee_id__phone"] + if item["assetassignment__assigned_by_employee_id__phone"] + else "-" + ), + "Gender": ( + item["assetassignment__assigned_by_employee_id__gender"] + if item["assetassignment__assigned_by_employee_id__gender"] + else "-" + ), + "Department": ( + item[ + "assetassignment__assigned_by_employee_id__employee_work_info__department_id__department" + ] + if item[ + "assetassignment__assigned_by_employee_id__employee_work_info__department_id__department" + ] + else "-" + ), + "Job Position": ( + item[ + "assetassignment__assigned_by_employee_id__employee_work_info__job_position_id__job_position" + ] + if item[ + "assetassignment__assigned_by_employee_id__employee_work_info__job_position_id__job_position" + ] + else "-" + ), + "Job Role": ( + item[ + "assetassignment__assigned_by_employee_id__employee_work_info__job_role_id__job_role" + ] + if item[ + "assetassignment__assigned_by_employee_id__employee_work_info__job_role_id__job_role" + ] + else "-" + ), + "Asset Purchce Date": item["asset_purchase_date"], + "Asset Cost": item["asset_purchase_cost"], + "Status": item["asset_status"], + "Assigned Date": ( + item["assetassignment__assigned_date"] + if item["assetassignment__assigned_date"] + else "-" + ), + "Return Date": ( + item["assetassignment__return_date"] + if item["assetassignment__return_date"] + else "-" + ), + "Return Condition": ( + item["assetassignment__return_status"] + if item["assetassignment__return_status"] + else "-" + ), + "Category": item["asset_category_id__asset_category_name"], + "Batch Number": item["asset_lot_number_id__lot_number"], + "Tracking ID": item["asset_tracking_id"], + "Expiry Date": item["expiry_date"] if item["expiry_date"] else "-", + } + for item in data ] return JsonResponse(data_list, safe=False) diff --git a/report/views/attendance_report.py b/report/views/attendance_report.py index d9d810102..95f635f9a 100644 --- a/report/views/attendance_report.py +++ b/report/views/attendance_report.py @@ -1,16 +1,15 @@ -from datetime import time,datetime - -from django.http import JsonResponse -from django.shortcuts import render +from datetime import datetime, time from django.apps import apps +from django.http import JsonResponse +from django.shortcuts import render if apps.is_installed("attendance"): from attendance.filters import AttendanceFilters + from attendance.models import Attendance from base.models import Company from horilla_views.cbv_methods import login_required, permission_required - from attendance.models import Attendance def convert_time_to_decimal_w(time_str): try: @@ -20,15 +19,13 @@ if apps.is_installed("attendance"): hours, minutes = time_str.hour, time_str.minute else: return "00.00" - + # Format as HH.MM formatted_time = f"{hours:02}.{minutes:02}" return formatted_time except (ValueError, TypeError): return "00.00" - - def convert_time_to_decimal(time_str): """Format time as HH.MM for aggregation.""" try: @@ -45,30 +42,53 @@ if apps.is_installed("attendance"): except Exception: return "00.00" - @login_required + @login_required @permission_required(perm="attendance.view_attendance") def attendance_report(request): - company = 'all' + company = "all" selected_company = request.session.get("selected_company") - if selected_company != 'all': + if selected_company != "all": company = Company.objects.filter(id=selected_company).first() - return render(request, "report/attendance_report.html",{'company':company,"f": AttendanceFilters() }) + return render( + request, + "report/attendance_report.html", + {"company": company, "f": AttendanceFilters()}, + ) @login_required @permission_required(perm="attendance.view_attendance") def attendance_pivot(request): - qs =Attendance.objects.all() + qs = Attendance.objects.all() filter_obj = AttendanceFilters(request.GET, queryset=qs) qs = filter_obj.qs - data = list(qs.values( - 'employee_id__employee_first_name','employee_id__employee_last_name','attendance_date','attendance_clock_in','attendance_clock_out', - 'attendance_worked_hour','minimum_hour','attendance_overtime','at_work_second','work_type_id__work_type','shift_id__employee_shift', - 'attendance_day__day','employee_id__gender','employee_id__email','employee_id__phone','employee_id__employee_work_info__department_id__department', 'employee_id__employee_work_info__job_role_id__job_role', - 'employee_id__employee_work_info__job_position_id__job_position', 'employee_id__employee_work_info__employee_type_id__employee_type', - 'employee_id__employee_work_info__experience', 'batch_attendance_id__title','employee_id__employee_work_info__company_id__company', - )) + data = list( + qs.values( + "employee_id__employee_first_name", + "employee_id__employee_last_name", + "attendance_date", + "attendance_clock_in", + "attendance_clock_out", + "attendance_worked_hour", + "minimum_hour", + "attendance_overtime", + "at_work_second", + "work_type_id__work_type", + "shift_id__employee_shift", + "attendance_day__day", + "employee_id__gender", + "employee_id__email", + "employee_id__phone", + "employee_id__employee_work_info__department_id__department", + "employee_id__employee_work_info__job_role_id__job_role", + "employee_id__employee_work_info__job_position_id__job_position", + "employee_id__employee_work_info__employee_type_id__employee_type", + "employee_id__employee_work_info__experience", + "batch_attendance_id__title", + "employee_id__employee_work_info__company_id__company", + ) + ) DAY = { "monday": "Monday", "tuesday": "Tuesday", @@ -89,44 +109,80 @@ if apps.is_installed("attendance"): "Gender": choice_gender.get(item["employee_id__gender"]), "Email": item["employee_id__email"], "Phone": item["employee_id__phone"], - "Department": item["employee_id__employee_work_info__department_id__department"] if item["employee_id__employee_work_info__department_id__department"] else "-", - "Job Position": item["employee_id__employee_work_info__job_position_id__job_position"] if item["employee_id__employee_work_info__job_position_id__job_position"] else "-", - "Job Role": item["employee_id__employee_work_info__job_role_id__job_role"] if item["employee_id__employee_work_info__job_role_id__job_role"] else "-", - "Work Type": item["work_type_id__work_type"] if item["work_type_id__work_type"] else "-", - "Shift": item["shift_id__employee_shift"] if item["shift_id__employee_shift"] else "-", + "Department": ( + item["employee_id__employee_work_info__department_id__department"] + if item[ + "employee_id__employee_work_info__department_id__department" + ] + else "-" + ), + "Job Position": ( + item[ + "employee_id__employee_work_info__job_position_id__job_position" + ] + if item[ + "employee_id__employee_work_info__job_position_id__job_position" + ] + else "-" + ), + "Job Role": ( + item["employee_id__employee_work_info__job_role_id__job_role"] + if item["employee_id__employee_work_info__job_role_id__job_role"] + else "-" + ), + "Work Type": ( + item["work_type_id__work_type"] + if item["work_type_id__work_type"] + else "-" + ), + "Shift": ( + item["shift_id__employee_shift"] + if item["shift_id__employee_shift"] + else "-" + ), "Experience": item["employee_id__employee_work_info__experience"], - "Attendance Date": item['attendance_date'], - "Attendance Day": DAY.get(item['attendance_day__day']), - "Clock-in": format_time(item['attendance_clock_in']), - "Clock-out": format_time(item['attendance_clock_out']), - "At Work": format_seconds_to_time(item['at_work_second']), - "Worked Hour": item['attendance_worked_hour'], - "Minimum Hour": item['minimum_hour'], - "Overtime": item['attendance_overtime'], - "Batch":item['batch_attendance_id__title'] if item['batch_attendance_id__title'] else "-", - "Company":item['employee_id__employee_work_info__company_id__company'], - + "Attendance Date": item["attendance_date"], + "Attendance Day": DAY.get(item["attendance_day__day"]), + "Clock-in": format_time(item["attendance_clock_in"]), + "Clock-out": format_time(item["attendance_clock_out"]), + "At Work": format_seconds_to_time(item["at_work_second"]), + "Worked Hour": item["attendance_worked_hour"], + "Minimum Hour": item["minimum_hour"], + "Overtime": item["attendance_overtime"], + "Batch": ( + item["batch_attendance_id__title"] + if item["batch_attendance_id__title"] + else "-" + ), + "Company": item["employee_id__employee_work_info__company_id__company"], # For correct total - "Clock-in Decimal": convert_time_to_decimal(item["attendance_clock_in"]), - "Clock-out Decimal": convert_time_to_decimal(item["attendance_clock_out"]), - "At Work Decimal": convert_time_to_decimal_w(format_seconds_to_time(item['at_work_second'])), - "Worked Hour Decimal": convert_time_to_decimal_w(item["attendance_worked_hour"]), - "Minimum Hour Decimal": convert_time_to_decimal_w(item["minimum_hour"]), - "Overtime Decimal": convert_time_to_decimal_w(item['attendance_overtime']), - + "Clock-in Decimal": convert_time_to_decimal( + item["attendance_clock_in"] + ), + "Clock-out Decimal": convert_time_to_decimal( + item["attendance_clock_out"] + ), + "At Work Decimal": convert_time_to_decimal_w( + format_seconds_to_time(item["at_work_second"]) + ), + "Worked Hour Decimal": convert_time_to_decimal_w( + item["attendance_worked_hour"] + ), + "Minimum Hour Decimal": convert_time_to_decimal_w(item["minimum_hour"]), + "Overtime Decimal": convert_time_to_decimal_w( + item["attendance_overtime"] + ), } for item in data ] return JsonResponse(data_list, safe=False) - # Helper function to format time def format_time(time_value): if isinstance(time_value, str): # In case time is string time_value = datetime.strptime(time_value, "%H:%M:%S").time() return time_value.strftime("%H:%M") if time_value else "" - def format_seconds_to_time(seconds): """Convert seconds to HH:MM format.""" try: @@ -136,4 +192,3 @@ if apps.is_installed("attendance"): return f"{hours:02}:{minutes:02}" except (ValueError, TypeError): return "00:00" - diff --git a/report/views/employee_report.py b/report/views/employee_report.py index f33a9541a..07c28adb4 100644 --- a/report/views/employee_report.py +++ b/report/views/employee_report.py @@ -3,54 +3,105 @@ from django.shortcuts import render from base.models import Company from employee.filters import EmployeeFilter -from horilla_views.cbv_methods import login_required, permission_required from employee.models import Employee +from horilla_views.cbv_methods import login_required, permission_required @login_required @permission_required(perm="employee.view_employee") def employee_report(request): - company = 'all' + company = "all" selected_company = request.session.get("selected_company") - if selected_company != 'all': + if selected_company != "all": company = Company.objects.filter(id=selected_company).first() - return render(request, "report/employee_report.html",{'company':company, "f":EmployeeFilter()}) + return render( + request, + "report/employee_report.html", + {"company": company, "f": EmployeeFilter()}, + ) + @login_required @permission_required(perm="employee.view_employee") def employee_pivot(request): qs = Employee.objects.all() - filtered_qs = EmployeeFilter(request.GET,queryset=qs) + filtered_qs = EmployeeFilter(request.GET, queryset=qs) qs = filtered_qs.qs - data = list(qs.values( - 'employee_first_name','employee_last_name', 'gender','email','phone','employee_work_info__department_id__department','employee_work_info__job_position_id__job_position', - 'employee_work_info__job_role_id__job_role','employee_work_info__work_type_id__work_type','employee_work_info__shift_id__employee_shift','employee_work_info__employee_type_id__employee_type', - 'employee_work_info__reporting_manager_id__employee_first_name','employee_work_info__reporting_manager_id__employee_last_name','employee_work_info__company_id__company','employee_work_info__date_joining', - 'employee_work_info__experience' - )) + data = list( + qs.values( + "employee_first_name", + "employee_last_name", + "gender", + "email", + "phone", + "employee_work_info__department_id__department", + "employee_work_info__job_position_id__job_position", + "employee_work_info__job_role_id__job_role", + "employee_work_info__work_type_id__work_type", + "employee_work_info__shift_id__employee_shift", + "employee_work_info__employee_type_id__employee_type", + "employee_work_info__reporting_manager_id__employee_first_name", + "employee_work_info__reporting_manager_id__employee_last_name", + "employee_work_info__company_id__company", + "employee_work_info__date_joining", + "employee_work_info__experience", + ) + ) choice_gender = { "male": "Male", "female": "Female", "other": "Other", } - + # Transform data to match format data_list = [ { - "Name": f"{item['employee_first_name']} {item['employee_last_name']}", + "Name": f"{item['employee_first_name']} {item['employee_last_name']}", "Gender": choice_gender.get(item["gender"]), "Email": item["email"], "Phone": item["phone"], - "Department": item["employee_work_info__department_id__department"] if item["employee_work_info__department_id__department"] else "-", - "Job Position": item["employee_work_info__job_position_id__job_position"] if item["employee_work_info__job_position_id__job_position"] else "-", - "Job Role": item["employee_work_info__job_role_id__job_role"] if item["employee_work_info__job_role_id__job_role"] else "-", - "Work Type": item["employee_work_info__work_type_id__work_type"] if item["employee_work_info__work_type_id__work_type"] else "-", - "Shift": item["employee_work_info__shift_id__employee_shift"] if item["employee_work_info__shift_id__employee_shift"] else "-", - "Employee Type": item["employee_work_info__employee_type_id__employee_type"] if item["employee_work_info__employee_type_id__employee_type"] else "-", - "Reporting Manager": f"{item['employee_work_info__reporting_manager_id__employee_first_name']} {item['employee_work_info__reporting_manager_id__employee_last_name']}" if item['employee_work_info__reporting_manager_id__employee_first_name'] else '-' , - "Date of Joining": item["employee_work_info__date_joining"] if item["employee_work_info__date_joining"] else '-', + "Department": ( + item["employee_work_info__department_id__department"] + if item["employee_work_info__department_id__department"] + else "-" + ), + "Job Position": ( + item["employee_work_info__job_position_id__job_position"] + if item["employee_work_info__job_position_id__job_position"] + else "-" + ), + "Job Role": ( + item["employee_work_info__job_role_id__job_role"] + if item["employee_work_info__job_role_id__job_role"] + else "-" + ), + "Work Type": ( + item["employee_work_info__work_type_id__work_type"] + if item["employee_work_info__work_type_id__work_type"] + else "-" + ), + "Shift": ( + item["employee_work_info__shift_id__employee_shift"] + if item["employee_work_info__shift_id__employee_shift"] + else "-" + ), + "Employee Type": ( + item["employee_work_info__employee_type_id__employee_type"] + if item["employee_work_info__employee_type_id__employee_type"] + else "-" + ), + "Reporting Manager": ( + f"{item['employee_work_info__reporting_manager_id__employee_first_name']} {item['employee_work_info__reporting_manager_id__employee_last_name']}" + if item["employee_work_info__reporting_manager_id__employee_first_name"] + else "-" + ), + "Date of Joining": ( + item["employee_work_info__date_joining"] + if item["employee_work_info__date_joining"] + else "-" + ), "Experience": round(float(item["employee_work_info__experience"] or 0), 2), "Company": item["employee_work_info__company_id__company"], } diff --git a/report/views/leave_report.py b/report/views/leave_report.py index fb3799550..dafefe30e 100644 --- a/report/views/leave_report.py +++ b/report/views/leave_report.py @@ -1,6 +1,6 @@ +from django.apps import apps from django.http import JsonResponse from django.shortcuts import render -from django.apps import apps if apps.is_installed("leave"): @@ -9,40 +9,63 @@ if apps.is_installed("leave"): from leave.filters import AssignedLeaveFilter, LeaveRequestFilter from leave.models import AvailableLeave, LeaveRequest - @login_required @permission_required(perm="leave.view_leaverequest") def leave_report(request): company = "all" selected_company = request.session.get("selected_company") - if selected_company != 'all': - company = Company.objects.filter(id = selected_company).first() + if selected_company != "all": + company = Company.objects.filter(id=selected_company).first() leave_request_filter = LeaveRequestFilter() - return render(request, "report/leave_report.html",{'company' : company, "form": leave_request_filter.form, "f": AssignedLeaveFilter(),} ) + return render( + request, + "report/leave_report.html", + { + "company": company, + "form": leave_request_filter.form, + "f": AssignedLeaveFilter(), + }, + ) @login_required @permission_required(perm="leave.view_leaverequest") def leave_pivot(request): - model_type = request.GET.get("model", "leave_request") # Default to LeaveRequest + model_type = request.GET.get( + "model", "leave_request" + ) # Default to LeaveRequest if model_type == "leave_request": - + qs = LeaveRequest.objects.all() leave_filter = LeaveRequestFilter(request.GET, queryset=qs) qs = leave_filter.qs - data = list(qs.values( - "employee_id__employee_first_name","employee_id__employee_last_name","leave_type_id__name", - "start_date","start_date_breakdown","end_date","end_date_breakdown","requested_days","status", - 'employee_id__gender','employee_id__email','employee_id__phone','employee_id__employee_work_info__department_id__department', - 'employee_id__employee_work_info__job_role_id__job_role','employee_id__employee_work_info__job_position_id__job_position', - 'employee_id__employee_work_info__employee_type_id__employee_type','employee_id__employee_work_info__experience', - 'employee_id__employee_work_info__work_type_id__work_type','employee_id__employee_work_info__shift_id__employee_shift', - 'employee_id__employee_work_info__company_id__company' - - )) + data = list( + qs.values( + "employee_id__employee_first_name", + "employee_id__employee_last_name", + "leave_type_id__name", + "start_date", + "start_date_breakdown", + "end_date", + "end_date_breakdown", + "requested_days", + "status", + "employee_id__gender", + "employee_id__email", + "employee_id__phone", + "employee_id__employee_work_info__department_id__department", + "employee_id__employee_work_info__job_role_id__job_role", + "employee_id__employee_work_info__job_position_id__job_position", + "employee_id__employee_work_info__employee_type_id__employee_type", + "employee_id__employee_work_info__experience", + "employee_id__employee_work_info__work_type_id__work_type", + "employee_id__employee_work_info__shift_id__employee_shift", + "employee_id__employee_work_info__company_id__company", + ) + ) BREAKDOWN_MAP = { "full_day": "Full Day", "first_half": "First Half", @@ -67,38 +90,95 @@ if apps.is_installed("leave"): "Gender": choice_gender.get(item["employee_id__gender"]), "Email": item["employee_id__email"], "Phone": item["employee_id__phone"], - "Department": item["employee_id__employee_work_info__department_id__department"] if item["employee_id__employee_work_info__department_id__department"] else "-", - "Job Position": item["employee_id__employee_work_info__job_position_id__job_position"] if item["employee_id__employee_work_info__job_position_id__job_position"] else "-", - "Job Role": item["employee_id__employee_work_info__job_role_id__job_role"] if item["employee_id__employee_work_info__job_role_id__job_role"] else "-", - "Work Type": item["employee_id__employee_work_info__work_type_id__work_type"] if item["employee_id__employee_work_info__work_type_id__work_type"] else "-", - "Shift": item["employee_id__employee_work_info__shift_id__employee_shift"] if item["employee_id__employee_work_info__shift_id__employee_shift"] else "-", + "Department": ( + item[ + "employee_id__employee_work_info__department_id__department" + ] + if item[ + "employee_id__employee_work_info__department_id__department" + ] + else "-" + ), + "Job Position": ( + item[ + "employee_id__employee_work_info__job_position_id__job_position" + ] + if item[ + "employee_id__employee_work_info__job_position_id__job_position" + ] + else "-" + ), + "Job Role": ( + item["employee_id__employee_work_info__job_role_id__job_role"] + if item[ + "employee_id__employee_work_info__job_role_id__job_role" + ] + else "-" + ), + "Work Type": ( + item["employee_id__employee_work_info__work_type_id__work_type"] + if item[ + "employee_id__employee_work_info__work_type_id__work_type" + ] + else "-" + ), + "Shift": ( + item[ + "employee_id__employee_work_info__shift_id__employee_shift" + ] + if item[ + "employee_id__employee_work_info__shift_id__employee_shift" + ] + else "-" + ), "Experience": item["employee_id__employee_work_info__experience"], "Leave Type": item["leave_type_id__name"], "Start Date": item["start_date"], - "Start Date Breakdown": BREAKDOWN_MAP.get(item["start_date_breakdown"], "-"), - "End Date Breakdown": BREAKDOWN_MAP.get(item["end_date_breakdown"], "-"), + "Start Date Breakdown": BREAKDOWN_MAP.get( + item["start_date_breakdown"], "-" + ), + "End Date Breakdown": BREAKDOWN_MAP.get( + item["end_date_breakdown"], "-" + ), "End Date": item["end_date"], "Requested Days": item["requested_days"], "Status": LEAVE_STATUS.get(item["status"]), - "Company":item['employee_id__employee_work_info__company_id__company'], + "Company": item[ + "employee_id__employee_work_info__company_id__company" + ], } for item in data ] elif model_type == "available_leave": qs = AvailableLeave.objects.all() - available_leave_filter = AssignedLeaveFilter(request.GET, queryset= qs) + available_leave_filter = AssignedLeaveFilter(request.GET, queryset=qs) qs = available_leave_filter.qs - data = list(qs.values( - "employee_id__employee_first_name","employee_id__employee_last_name","leave_type_id__name", - "available_days","carryforward_days","total_leave_days","assigned_date","reset_date","expired_date", - 'employee_id__gender','employee_id__email','employee_id__phone','employee_id__employee_work_info__department_id__department', - 'employee_id__employee_work_info__job_role_id__job_role','employee_id__employee_work_info__job_position_id__job_position', - 'employee_id__employee_work_info__employee_type_id__employee_type','employee_id__employee_work_info__experience', - 'employee_id__employee_work_info__work_type_id__work_type','employee_id__employee_work_info__shift_id__employee_shift', - 'employee_id__employee_work_info__company_id__company', - )) + data = list( + qs.values( + "employee_id__employee_first_name", + "employee_id__employee_last_name", + "leave_type_id__name", + "available_days", + "carryforward_days", + "total_leave_days", + "assigned_date", + "reset_date", + "expired_date", + "employee_id__gender", + "employee_id__email", + "employee_id__phone", + "employee_id__employee_work_info__department_id__department", + "employee_id__employee_work_info__job_role_id__job_role", + "employee_id__employee_work_info__job_position_id__job_position", + "employee_id__employee_work_info__employee_type_id__employee_type", + "employee_id__employee_work_info__experience", + "employee_id__employee_work_info__work_type_id__work_type", + "employee_id__employee_work_info__shift_id__employee_shift", + "employee_id__employee_work_info__company_id__company", + ) + ) choice_gender = { "male": "Male", "female": "Female", @@ -110,11 +190,47 @@ if apps.is_installed("leave"): "Gender": choice_gender.get(item["employee_id__gender"]), "Email": item["employee_id__email"], "Phone": item["employee_id__phone"], - "Department": item["employee_id__employee_work_info__department_id__department"] if item["employee_id__employee_work_info__department_id__department"] else "-", - "Job Position": item["employee_id__employee_work_info__job_position_id__job_position"] if item["employee_id__employee_work_info__job_position_id__job_position"] else "-", - "Job Role": item["employee_id__employee_work_info__job_role_id__job_role"] if item["employee_id__employee_work_info__job_role_id__job_role"] else "-", - "Work Type": item["employee_id__employee_work_info__work_type_id__work_type"] if item["employee_id__employee_work_info__work_type_id__work_type"] else "-", - "Shift": item["employee_id__employee_work_info__shift_id__employee_shift"] if item["employee_id__employee_work_info__shift_id__employee_shift"] else "-", + "Department": ( + item[ + "employee_id__employee_work_info__department_id__department" + ] + if item[ + "employee_id__employee_work_info__department_id__department" + ] + else "-" + ), + "Job Position": ( + item[ + "employee_id__employee_work_info__job_position_id__job_position" + ] + if item[ + "employee_id__employee_work_info__job_position_id__job_position" + ] + else "-" + ), + "Job Role": ( + item["employee_id__employee_work_info__job_role_id__job_role"] + if item[ + "employee_id__employee_work_info__job_role_id__job_role" + ] + else "-" + ), + "Work Type": ( + item["employee_id__employee_work_info__work_type_id__work_type"] + if item[ + "employee_id__employee_work_info__work_type_id__work_type" + ] + else "-" + ), + "Shift": ( + item[ + "employee_id__employee_work_info__shift_id__employee_shift" + ] + if item[ + "employee_id__employee_work_info__shift_id__employee_shift" + ] + else "-" + ), "Experience": item["employee_id__employee_work_info__experience"], "Leave Type": item["leave_type_id__name"], "Available Days": item["available_days"], @@ -123,7 +239,9 @@ if apps.is_installed("leave"): "Assigned Date": item["assigned_date"], "Reset Date": item.get("reset_date", "-") or "-", "Expired Date": item.get("expired_date", "-") or "-", - "Company":item['employee_id__employee_work_info__company_id__company'], + "Company": item[ + "employee_id__employee_work_info__company_id__company" + ], } for item in data ] diff --git a/report/views/payroll_report.py b/report/views/payroll_report.py index e3e317ef4..8c03aacdc 100644 --- a/report/views/payroll_report.py +++ b/report/views/payroll_report.py @@ -1,7 +1,7 @@ +from django.apps import apps from django.http import JsonResponse from django.shortcuts import render from django.utils.dateparse import parse_date -from django.apps import apps if apps.is_installed("payroll"): @@ -10,30 +10,35 @@ if apps.is_installed("payroll"): from payroll.filters import PayslipFilter from payroll.models.models import Payslip - @login_required @permission_required(perm="payroll.view_payslip") def payroll_report(request): - company = 'all' + company = "all" selected_company = request.session.get("selected_company") - if selected_company != 'all': + if selected_company != "all": company = Company.objects.filter(id=selected_company).first() - + if request.user.has_perm("payroll.view_payslip"): payslips = Payslip.objects.all() else: - payslips = Payslip.objects.filter(employee_id__employee_user_id=request.user) + payslips = Payslip.objects.filter( + employee_id__employee_user_id=request.user + ) filter_form = PayslipFilter(request.GET, payslips) - return render(request, "report/payroll_report.html",{'company':company,"f":filter_form}) + return render( + request, + "report/payroll_report.html", + {"company": company, "f": filter_form}, + ) @login_required @permission_required(perm="payroll.view_payslip") def payroll_pivot(request): model_type = request.GET.get("model", "payslip") - if model_type == 'payslip': + if model_type == "payslip": qs = Payslip.objects.all() if employee_id := request.GET.getlist("employee_id"): @@ -56,7 +61,7 @@ if apps.is_installed("payroll"): qs = qs.filter(end_date__gte=end_date_from) if end_date_to: qs = qs.filter(end_date__lte=end_date_to) - + # Gross Pay Range gross_pay_gte = request.GET.get("gross_pay__gte") gross_pay_lte = request.GET.get("gross_pay__lte") @@ -81,16 +86,32 @@ if apps.is_installed("payroll"): if net_pay_lte: qs = qs.filter(net_pay__lte=net_pay_lte) - - data = list(qs.values( - 'id', # Include payslip ID to fetch pay_head_data later - 'employee_id__employee_first_name', 'employee_id__employee_last_name', 'employee_id__gender', 'employee_id__email', - 'employee_id__phone', 'start_date', 'end_date', 'contract_wage', 'basic_pay', 'gross_pay', 'deduction', 'net_pay','group_name', - 'status', 'employee_id__employee_work_info__department_id__department', 'employee_id__employee_work_info__job_role_id__job_role', - 'employee_id__employee_work_info__job_position_id__job_position', 'employee_id__employee_work_info__work_type_id__work_type', - 'employee_id__employee_work_info__shift_id__employee_shift', 'employee_id__employee_work_info__employee_type_id__employee_type', - 'employee_id__employee_work_info__experience', - )) + data = list( + qs.values( + "id", # Include payslip ID to fetch pay_head_data later + "employee_id__employee_first_name", + "employee_id__employee_last_name", + "employee_id__gender", + "employee_id__email", + "employee_id__phone", + "start_date", + "end_date", + "contract_wage", + "basic_pay", + "gross_pay", + "deduction", + "net_pay", + "group_name", + "status", + "employee_id__employee_work_info__department_id__department", + "employee_id__employee_work_info__job_role_id__job_role", + "employee_id__employee_work_info__job_position_id__job_position", + "employee_id__employee_work_info__work_type_id__work_type", + "employee_id__employee_work_info__shift_id__employee_shift", + "employee_id__employee_work_info__employee_type_id__employee_type", + "employee_id__employee_work_info__experience", + ) + ) choice_gender = { "male": "Male", @@ -102,12 +123,16 @@ if apps.is_installed("payroll"): "draft": "Draft", "review_ongoing": "Review Ongoing", "confirmed": "Confirmed", - "paid": "Paid" + "paid": "Paid", } # Fetch pay_head_data separately and map by payslip ID payslip_ids = [item["id"] for item in data] - pay_head_data_dict = dict(Payslip.objects.filter(id__in=payslip_ids).values_list("id", "pay_head_data")) + pay_head_data_dict = dict( + Payslip.objects.filter(id__in=payslip_ids).values_list( + "id", "pay_head_data" + ) + ) data_list = [] for item in data: @@ -116,59 +141,136 @@ if apps.is_installed("payroll"): # Extract allowances and deductions allowances = pay_head_data.get("allowances", []) - deductions = ( - pay_head_data.get("pretax_deductions", []) + pay_head_data.get("post_tax_deductions", []) - ) + deductions = pay_head_data.get( + "pretax_deductions", [] + ) + pay_head_data.get("post_tax_deductions", []) # Prepare allowance and deduction lists with properly rounded amounts - allowance_titles = ", ".join([allowance["title"] for allowance in allowances]) or "-" - allowance_amounts = ", ".join( - [str(round(float(allowance["amount"] or 0), 2)) for allowance in allowances] - ) or "-" + allowance_titles = ( + ", ".join([allowance["title"] for allowance in allowances]) or "-" + ) + allowance_amounts = ( + ", ".join( + [ + str(round(float(allowance["amount"] or 0), 2)) + for allowance in allowances + ] + ) + or "-" + ) - deduction_titles = ", ".join([deduction["title"] for deduction in deductions]) or "-" - deduction_amounts = ", ".join( - [str(round(float(deduction["amount"] or 0), 2)) for deduction in deductions] - ) or "-" + deduction_titles = ( + ", ".join([deduction["title"] for deduction in deductions]) or "-" + ) + deduction_amounts = ( + ", ".join( + [ + str(round(float(deduction["amount"] or 0), 2)) + for deduction in deductions + ] + ) + or "-" + ) # Calculate total allowance amount total_allowance_amount = sum( - [round(float(allowance["amount"] or 0), 2) for allowance in allowances] + [ + round(float(allowance["amount"] or 0), 2) + for allowance in allowances + ] ) # Calculate total deduction amount total_deduction_amount = sum( - [round(float(deduction["amount"] or 0), 2) for deduction in deductions] + [ + round(float(deduction["amount"] or 0), 2) + for deduction in deductions + ] ) # Main data structure - data_list.append({ - "Employee": f"{item['employee_id__employee_first_name']} {item['employee_id__employee_last_name']}", - "Gender": choice_gender.get(item["employee_id__gender"]), - "Email": item["employee_id__email"], - "Phone": item["employee_id__phone"], - "Department": item["employee_id__employee_work_info__department_id__department"] if item["employee_id__employee_work_info__department_id__department"] else "-", - "Job Position": item["employee_id__employee_work_info__job_position_id__job_position"] if item["employee_id__employee_work_info__job_position_id__job_position"] else "-", - "Job Role": item["employee_id__employee_work_info__job_role_id__job_role"] if item["employee_id__employee_work_info__job_role_id__job_role"] else "-", - "Work Type": item["employee_id__employee_work_info__work_type_id__work_type"] if item["employee_id__employee_work_info__work_type_id__work_type"] else "-", - "Shift": item["employee_id__employee_work_info__shift_id__employee_shift"] if item["employee_id__employee_work_info__shift_id__employee_shift"] else "-", - "Employee Type": item["employee_id__employee_work_info__employee_type_id__employee_type"] if item["employee_id__employee_work_info__employee_type_id__employee_type"] else "-", - "Payslip Start Date": item["start_date"], - "Payslip End Date": item["end_date"], - "Batch Name": item['group_name'] if item['group_name'] else '-', - "Contract Wage": round(float(item["contract_wage"] or 0), 2), - "Basic Salary": round(float(item["basic_pay"] or 0), 2), - "Gross Pay": round(float(item["gross_pay"] or 0), 2), - "Net Pay": round(float(item["net_pay"] or 0), 2), - "Allowance Title": allowance_titles, - "Allowance Amount": allowance_amounts, - "Total Allowance Amount": round(total_allowance_amount, 2), - "Deduction Title": deduction_titles, - "Deduction Amount": deduction_amounts, - "Total Deduction Amount": round(total_deduction_amount, 2), - "Status": STATUS.get(item["status"]), - "Experience": round(float(item["employee_id__employee_work_info__experience"] or 0), 2), - }) + data_list.append( + { + "Employee": f"{item['employee_id__employee_first_name']} {item['employee_id__employee_last_name']}", + "Gender": choice_gender.get(item["employee_id__gender"]), + "Email": item["employee_id__email"], + "Phone": item["employee_id__phone"], + "Department": ( + item[ + "employee_id__employee_work_info__department_id__department" + ] + if item[ + "employee_id__employee_work_info__department_id__department" + ] + else "-" + ), + "Job Position": ( + item[ + "employee_id__employee_work_info__job_position_id__job_position" + ] + if item[ + "employee_id__employee_work_info__job_position_id__job_position" + ] + else "-" + ), + "Job Role": ( + item[ + "employee_id__employee_work_info__job_role_id__job_role" + ] + if item[ + "employee_id__employee_work_info__job_role_id__job_role" + ] + else "-" + ), + "Work Type": ( + item[ + "employee_id__employee_work_info__work_type_id__work_type" + ] + if item[ + "employee_id__employee_work_info__work_type_id__work_type" + ] + else "-" + ), + "Shift": ( + item[ + "employee_id__employee_work_info__shift_id__employee_shift" + ] + if item[ + "employee_id__employee_work_info__shift_id__employee_shift" + ] + else "-" + ), + "Employee Type": ( + item[ + "employee_id__employee_work_info__employee_type_id__employee_type" + ] + if item[ + "employee_id__employee_work_info__employee_type_id__employee_type" + ] + else "-" + ), + "Payslip Start Date": item["start_date"], + "Payslip End Date": item["end_date"], + "Batch Name": item["group_name"] if item["group_name"] else "-", + "Contract Wage": round(float(item["contract_wage"] or 0), 2), + "Basic Salary": round(float(item["basic_pay"] or 0), 2), + "Gross Pay": round(float(item["gross_pay"] or 0), 2), + "Net Pay": round(float(item["net_pay"] or 0), 2), + "Allowance Title": allowance_titles, + "Allowance Amount": allowance_amounts, + "Total Allowance Amount": round(total_allowance_amount, 2), + "Deduction Title": deduction_titles, + "Deduction Amount": deduction_amounts, + "Total Deduction Amount": round(total_deduction_amount, 2), + "Status": STATUS.get(item["status"]), + "Experience": round( + float( + item["employee_id__employee_work_info__experience"] or 0 + ), + 2, + ), + } + ) elif model_type == "allowance": @@ -177,13 +279,24 @@ if apps.is_installed("payroll"): payslip_filter = PayslipFilter(request.GET, queryset=payslips) filtered_qs = payslip_filter.qs # This uses all custom filters you defined - data = list(filtered_qs.values( - 'id', # Include payslip ID to fetch pay_head_data later - 'employee_id__employee_first_name', 'employee_id__employee_last_name', 'employee_id__gender', 'employee_id__email', - 'employee_id__phone', 'start_date', 'end_date','status', 'employee_id__employee_work_info__department_id__department', - 'employee_id__employee_work_info__job_role_id__job_role','employee_id__employee_work_info__job_position_id__job_position', - 'employee_id__employee_work_info__work_type_id__work_type','employee_id__employee_work_info__shift_id__employee_shift', - )) + data = list( + filtered_qs.values( + "id", # Include payslip ID to fetch pay_head_data later + "employee_id__employee_first_name", + "employee_id__employee_last_name", + "employee_id__gender", + "employee_id__email", + "employee_id__phone", + "start_date", + "end_date", + "status", + "employee_id__employee_work_info__department_id__department", + "employee_id__employee_work_info__job_role_id__job_role", + "employee_id__employee_work_info__job_position_id__job_position", + "employee_id__employee_work_info__work_type_id__work_type", + "employee_id__employee_work_info__shift_id__employee_shift", + ) + ) choice_gender = { "male": "Male", @@ -195,12 +308,16 @@ if apps.is_installed("payroll"): "draft": "Draft", "review_ongoing": "Review Ongoing", "confirmed": "Confirmed", - "paid": "Paid" + "paid": "Paid", } # Fetch pay_head_data separately and map by payslip ID payslip_ids = [item["id"] for item in data] - pay_head_data_dict = dict(Payslip.objects.filter(id__in=payslip_ids).values_list("id", "pay_head_data")) + pay_head_data_dict = dict( + Payslip.objects.filter(id__in=payslip_ids).values_list( + "id", "pay_head_data" + ) + ) data_list = [] for item in data: @@ -212,43 +329,88 @@ if apps.is_installed("payroll"): # Add Allowances to combined data for allowance in pay_head_data.get("allowances", []): - all_pay_data.append({ - "Pay Type": "Allowance", - "Title": allowance["title"], - "Amount": round(float(allowance["amount"] or 0), 2), - }) + all_pay_data.append( + { + "Pay Type": "Allowance", + "Title": allowance["title"], + "Amount": round(float(allowance["amount"] or 0), 2), + } + ) # Add Deductions to combined data - for deduction in ( - pay_head_data.get("pretax_deductions", []) + pay_head_data.get("post_tax_deductions", []) - ): - all_pay_data.append({ - "Pay Type": "Deduction", - "Title": deduction["title"], - "Amount": round(float(deduction["amount"] or 0), 2), - }) + for deduction in pay_head_data.get( + "pretax_deductions", [] + ) + pay_head_data.get("post_tax_deductions", []): + all_pay_data.append( + { + "Pay Type": "Deduction", + "Title": deduction["title"], + "Amount": round(float(deduction["amount"] or 0), 2), + } + ) # Add combined data to main data list for pay_item in all_pay_data: - data_list.append({ - "Employee": f"{item['employee_id__employee_first_name']} {item['employee_id__employee_last_name']}", - "Gender": choice_gender.get(item["employee_id__gender"]), - "Email": item["employee_id__email"], - "Phone": item["employee_id__phone"], - "Department": item["employee_id__employee_work_info__department_id__department"] if item["employee_id__employee_work_info__department_id__department"] else "-", - "Job Position": item["employee_id__employee_work_info__job_position_id__job_position"] if item["employee_id__employee_work_info__job_position_id__job_position"] else "-", - "Job Role": item["employee_id__employee_work_info__job_role_id__job_role"] if item["employee_id__employee_work_info__job_role_id__job_role"] else "-", - "Work Type": item["employee_id__employee_work_info__work_type_id__work_type"] if item["employee_id__employee_work_info__work_type_id__work_type"] else "-", - "Shift": item["employee_id__employee_work_info__shift_id__employee_shift"] if item["employee_id__employee_work_info__shift_id__employee_shift"] else "-", - "Payslip Start Date": item["start_date"], - "Payslip End Date": item["end_date"], - "Allowance & Deduction": pay_item["Pay Type"], - "Allowance & Deduction Title": pay_item["Title"], - "Allowance & Deduction Amount": pay_item["Amount"], - "Status": STATUS.get(item["status"]), - }) + data_list.append( + { + "Employee": f"{item['employee_id__employee_first_name']} {item['employee_id__employee_last_name']}", + "Gender": choice_gender.get(item["employee_id__gender"]), + "Email": item["employee_id__email"], + "Phone": item["employee_id__phone"], + "Department": ( + item[ + "employee_id__employee_work_info__department_id__department" + ] + if item[ + "employee_id__employee_work_info__department_id__department" + ] + else "-" + ), + "Job Position": ( + item[ + "employee_id__employee_work_info__job_position_id__job_position" + ] + if item[ + "employee_id__employee_work_info__job_position_id__job_position" + ] + else "-" + ), + "Job Role": ( + item[ + "employee_id__employee_work_info__job_role_id__job_role" + ] + if item[ + "employee_id__employee_work_info__job_role_id__job_role" + ] + else "-" + ), + "Work Type": ( + item[ + "employee_id__employee_work_info__work_type_id__work_type" + ] + if item[ + "employee_id__employee_work_info__work_type_id__work_type" + ] + else "-" + ), + "Shift": ( + item[ + "employee_id__employee_work_info__shift_id__employee_shift" + ] + if item[ + "employee_id__employee_work_info__shift_id__employee_shift" + ] + else "-" + ), + "Payslip Start Date": item["start_date"], + "Payslip End Date": item["end_date"], + "Allowance & Deduction": pay_item["Pay Type"], + "Allowance & Deduction Title": pay_item["Title"], + "Allowance & Deduction Amount": pay_item["Amount"], + "Status": STATUS.get(item["status"]), + } + ) else: data_list = [] return JsonResponse(data_list, safe=False) - diff --git a/report/views/pms_report.py b/report/views/pms_report.py index 29d7027ca..d5ab7147f 100644 --- a/report/views/pms_report.py +++ b/report/views/pms_report.py @@ -1,6 +1,6 @@ +from django.apps import apps from django.http import JsonResponse from django.shortcuts import render -from django.apps import apps if apps.is_installed("pms"): @@ -10,22 +10,23 @@ if apps.is_installed("pms"): from pms.models import EmployeeKeyResult, EmployeeObjective, Feedback, Objective from pms.views import objective_filter_pagination - @login_required @permission_required(perm="pms.view_objective") def pms_report(request): - company = 'all' + company = "all" selected_company = request.session.get("selected_company") - if selected_company != 'all': + if selected_company != "all": company = Company.objects.filter(id=selected_company).first() employee = request.user.employee_get objective_own = EmployeeObjective.objects.filter( - employee_id=employee, archive=False + employee_id=employee, archive=False ) objective_own = objective_own.distinct() - feedback = request.GET.get("search") # if the search is none the filter will works + feedback = request.GET.get( + "search" + ) # if the search is none the filter will works if feedback is None: feedback = "" self_feedback = Feedback.objects.filter(employee_id=employee).filter( @@ -37,17 +38,21 @@ if apps.is_installed("pms"): ) context = objective_filter_pagination(request, objective_own) - cm = {'company':company,"feedback_filter_form":feedback_filter_own.form,"emp_obj_form": EmployeeObjectiveFilter()} + cm = { + "company": company, + "feedback_filter_form": feedback_filter_own.form, + "emp_obj_form": EmployeeObjectiveFilter(), + } context.update(cm) - return render(request, "report/pms_report.html",context) + return render(request, "report/pms_report.html", context) @login_required @permission_required(perm="pms.view_objective") def pms_pivot(request): - - model_type = request.GET.get('model', 'objective') - if model_type == 'objective': + + model_type = request.GET.get("model", "objective") + if model_type == "objective": qs = Objective.objects.all() if managers := request.GET.getlist("managers"): @@ -59,41 +64,74 @@ if apps.is_installed("pms"): if key_result_id := request.GET.get("employee_objective__key_result_id"): qs = qs.filter(key_result_id=key_result_id) - data = list(qs.values( - "title","managers__employee_first_name","managers__employee_last_name", - "assignees__employee_first_name","assignees__employee_last_name", - "key_result_id__title","key_result_id__target_value","duration_unit","duration", - "company_id__company","key_result_id__progress_type","key_result_id__duration", - 'assignees__employee_work_info__department_id__department', 'assignees__employee_work_info__job_role_id__job_role', - 'assignees__employee_work_info__job_position_id__job_position', - )) + data = list( + qs.values( + "title", + "managers__employee_first_name", + "managers__employee_last_name", + "assignees__employee_first_name", + "assignees__employee_last_name", + "key_result_id__title", + "key_result_id__target_value", + "duration_unit", + "duration", + "company_id__company", + "key_result_id__progress_type", + "key_result_id__duration", + "assignees__employee_work_info__department_id__department", + "assignees__employee_work_info__job_role_id__job_role", + "assignees__employee_work_info__job_position_id__job_position", + ) + ) DURATION_UNIT = { - "days" :"Days", - "months" :"Months", - "years" :"Years", + "days": "Days", + "months": "Months", + "years": "Years", } KEY_RESULT_TARGET = { - "%" :"%", - "#" :"Number", - "Currency" :"Currency", + "%": "%", + "#": "Number", + "Currency": "Currency", } data_list = [ { - "Objective":item["title"], - "Objective Duration":f'{item["duration"]} {DURATION_UNIT.get(item["duration_unit"])}', - "Manager":f"{item['managers__employee_first_name']} {item['managers__employee_last_name']}" if item['managers__employee_first_name'] else "-", - "Assignees":f"{item['assignees__employee_first_name']} {item['assignees__employee_last_name']}", - "Assignee Department":item["assignees__employee_work_info__department_id__department"] if item["assignees__employee_work_info__department_id__department"] else "-", - "Assignee Job Position":item["assignees__employee_work_info__job_position_id__job_position"] if item["assignees__employee_work_info__job_position_id__job_position"] else "-", - "Assignee Job Role":item["assignees__employee_work_info__job_role_id__job_role"] if item["assignees__employee_work_info__job_role_id__job_role"] else"-", - "Key Results":item["key_result_id__title"], - "Key Result Duration":f'{item["key_result_id__duration"]} {"Days"}', - "Key Result Target":f'{item["key_result_id__target_value"]} {KEY_RESULT_TARGET.get(item["key_result_id__progress_type"])}', - "Company":item["company_id__company"] - - }for item in data + "Objective": item["title"], + "Objective Duration": f'{item["duration"]} {DURATION_UNIT.get(item["duration_unit"])}', + "Manager": ( + f"{item['managers__employee_first_name']} {item['managers__employee_last_name']}" + if item["managers__employee_first_name"] + else "-" + ), + "Assignees": f"{item['assignees__employee_first_name']} {item['assignees__employee_last_name']}", + "Assignee Department": ( + item["assignees__employee_work_info__department_id__department"] + if item[ + "assignees__employee_work_info__department_id__department" + ] + else "-" + ), + "Assignee Job Position": ( + item[ + "assignees__employee_work_info__job_position_id__job_position" + ] + if item[ + "assignees__employee_work_info__job_position_id__job_position" + ] + else "-" + ), + "Assignee Job Role": ( + item["assignees__employee_work_info__job_role_id__job_role"] + if item["assignees__employee_work_info__job_role_id__job_role"] + else "-" + ), + "Key Results": item["key_result_id__title"], + "Key Result Duration": f'{item["key_result_id__duration"]} {"Days"}', + "Key Result Target": f'{item["key_result_id__target_value"]} {KEY_RESULT_TARGET.get(item["key_result_id__progress_type"])}', + "Company": item["company_id__company"], + } + for item in data ] - elif model_type == 'feedback': + elif model_type == "feedback": data_list = [] @@ -106,13 +144,13 @@ if apps.is_installed("pms"): feedbacks = Feedback.objects.select_related( "manager_id", "employee_id", "question_template_id" ).prefetch_related( - "colleague_id", "subordinate_id", + "colleague_id", + "subordinate_id", "question_template_id__question", "feedback_answer__question_id", # related_name "feedback_answer__employee_id", ) - # ✅ FILTERS added here if review_cycle := request.GET.get("review_cycle"): feedbacks = feedbacks.filter(review_cycle=review_cycle) @@ -131,63 +169,102 @@ if apps.is_installed("pms"): if end_date := request.GET.get("end_date"): feedbacks = feedbacks.filter(created_at__date__lte=end_date) - for feedback in feedbacks: - manager = f"{feedback.manager_id.employee_first_name} {feedback.manager_id.employee_last_name}" if feedback.manager_id else "" - employee = f"{feedback.employee_id.employee_first_name} {feedback.employee_id.employee_last_name}" if feedback.employee_id else "" + manager = ( + f"{feedback.manager_id.employee_first_name} {feedback.manager_id.employee_last_name}" + if feedback.manager_id + else "" + ) + employee = ( + f"{feedback.employee_id.employee_first_name} {feedback.employee_id.employee_last_name}" + if feedback.employee_id + else "" + ) - answerable_employees = list(feedback.colleague_id.all()) + list(feedback.subordinate_id.all()) - answerable_names = ', '.join( - f"{e.employee_first_name} {e.employee_last_name}" for e in answerable_employees - ) or "-" + answerable_employees = list(feedback.colleague_id.all()) + list( + feedback.subordinate_id.all() + ) + answerable_names = ( + ", ".join( + f"{e.employee_first_name} {e.employee_last_name}" + for e in answerable_employees + ) + or "-" + ) questions = feedback.question_template_id.question.all() # Fetch ALL answers for this feedback and map them grouped by question - answers = feedback.feedback_answer.select_related("employee_id", "question_id") + answers = feedback.feedback_answer.select_related( + "employee_id", "question_id" + ) for question in questions: - question_answers = [ans for ans in answers if ans.question_id_id == question.id] + question_answers = [ + ans for ans in answers if ans.question_id_id == question.id + ] # If no one answered this question, still show the question if not question_answers: - data_list.append({ - "Title":feedback.review_cycle, - "Manager": manager, - "Employee": employee, - "Answerable Employees": answerable_names, - "Questions": question.question, - "Answer": "", - "Answered Employees": "-", - "Status": feedback.status, - "Start Date": feedback.start_date, - "End Date": feedback.end_date, - "Is Cyclic": "Yes" if feedback.cyclic_feedback else "No", - "Cycle Period": f"{feedback.cyclic_feedback_days_count} {PERIOD.get(feedback.cyclic_feedback_period)}" if feedback.cyclic_feedback_days_count else "-" - }) - else: - for answer in question_answers: - answer_value = answer.answer.get("answer") if answer.answer else "" - answered_by = f"{answer.employee_id.employee_first_name} {answer.employee_id.employee_last_name}" if answer.employee_id else "-" - data_list.append({ - "Title":feedback.review_cycle, + data_list.append( + { + "Title": feedback.review_cycle, "Manager": manager, "Employee": employee, "Answerable Employees": answerable_names, "Questions": question.question, - "Answer": answer_value, - "Answered Employees": answered_by, + "Answer": "", + "Answered Employees": "-", "Status": feedback.status, "Start Date": feedback.start_date, "End Date": feedback.end_date, - "Is Cyclic": "Yes" if feedback.cyclic_feedback else "No", - "Cycle Period": f"{feedback.cyclic_feedback_days_count} {PERIOD.get(feedback.cyclic_feedback_period)}" if feedback.cyclic_feedback_days_count else "-" - }) - elif model_type == 'employeeobjective': + "Is Cyclic": ( + "Yes" if feedback.cyclic_feedback else "No" + ), + "Cycle Period": ( + f"{feedback.cyclic_feedback_days_count} {PERIOD.get(feedback.cyclic_feedback_period)}" + if feedback.cyclic_feedback_days_count + else "-" + ), + } + ) + else: + for answer in question_answers: + answer_value = ( + answer.answer.get("answer") if answer.answer else "" + ) + answered_by = ( + f"{answer.employee_id.employee_first_name} {answer.employee_id.employee_last_name}" + if answer.employee_id + else "-" + ) + data_list.append( + { + "Title": feedback.review_cycle, + "Manager": manager, + "Employee": employee, + "Answerable Employees": answerable_names, + "Questions": question.question, + "Answer": answer_value, + "Answered Employees": answered_by, + "Status": feedback.status, + "Start Date": feedback.start_date, + "End Date": feedback.end_date, + "Is Cyclic": ( + "Yes" if feedback.cyclic_feedback else "No" + ), + "Cycle Period": ( + f"{feedback.cyclic_feedback_days_count} {PERIOD.get(feedback.cyclic_feedback_period)}" + if feedback.cyclic_feedback_days_count + else "-" + ), + } + ) + elif model_type == "employeeobjective": from django.utils.dateparse import parse_date - qs=EmployeeKeyResult.objects.all() + qs = EmployeeKeyResult.objects.all() # Filter section if assignees := request.GET.getlist("employee_id"): @@ -211,44 +288,87 @@ if apps.is_installed("pms"): if end_date_to: qs = qs.filter(end_date__lte=end_date_to) - data = list(qs.values( - "key_result","employee_objective_id__employee_id__employee_first_name","employee_objective_id__employee_id__employee_last_name", - "employee_objective_id__objective_id__title","employee_objective_id__objective_id__duration_unit","employee_objective_id__objective_id__duration", - "start_value","current_value","target_value","start_date","end_date","status","progress_type",'employee_objective_id__employee_id__employee_work_info__department_id__department', - 'employee_objective_id__employee_id__employee_work_info__job_role_id__job_role','employee_objective_id__employee_id__employee_work_info__job_position_id__job_position', - - )) + data = list( + qs.values( + "key_result", + "employee_objective_id__employee_id__employee_first_name", + "employee_objective_id__employee_id__employee_last_name", + "employee_objective_id__objective_id__title", + "employee_objective_id__objective_id__duration_unit", + "employee_objective_id__objective_id__duration", + "start_value", + "current_value", + "target_value", + "start_date", + "end_date", + "status", + "progress_type", + "employee_objective_id__employee_id__employee_work_info__department_id__department", + "employee_objective_id__employee_id__employee_work_info__job_role_id__job_role", + "employee_objective_id__employee_id__employee_work_info__job_position_id__job_position", + ) + ) DURATION_UNIT = { - "days" :"Days", - "months" :"Months", - "years" :"Years", + "days": "Days", + "months": "Months", + "years": "Years", } KEY_RESULT_TARGET = { - "%" :"%", - "#" :"Number", - "Currency" :"Currency", + "%": "%", + "#": "Number", + "Currency": "Currency", } data_list = [ { "Employee": f"{item['employee_objective_id__employee_id__employee_first_name']} {item['employee_objective_id__employee_id__employee_last_name']}", - "Department":item["employee_objective_id__employee_id__employee_work_info__department_id__department"] if item["employee_objective_id__employee_id__employee_work_info__department_id__department"] else "-", - "Job Position":item["employee_objective_id__employee_id__employee_work_info__job_position_id__job_position"] if item["employee_objective_id__employee_id__employee_work_info__job_position_id__job_position"] else "-", - "Job Role":item["employee_objective_id__employee_id__employee_work_info__job_role_id__job_role"] if item["employee_objective_id__employee_id__employee_work_info__job_role_id__job_role"] else "-", - "Employee Keyresult":item["key_result"], - "Objective":item["employee_objective_id__objective_id__title"], - "Objective Duration":f'{item["employee_objective_id__objective_id__duration"]} {DURATION_UNIT.get(item["employee_objective_id__objective_id__duration_unit"])}', - "Keyresult Start Value":f'{item["start_value"]} {KEY_RESULT_TARGET.get(item["progress_type"])}', - "Keyresult Target Value":f'{item["target_value"]} {KEY_RESULT_TARGET.get(item["progress_type"])}', - "Keyresult Current Value":f'{item["current_value"]} {KEY_RESULT_TARGET.get(item["progress_type"])}' if item["current_value"] else "-", - "Keyresult Start Date":item["start_date"] if item["start_date"] else "-", - "Keyresult End Date":item["end_date"] if item["end_date"] else "-", - "status":item["status"], - - }for item in data + "Department": ( + item[ + "employee_objective_id__employee_id__employee_work_info__department_id__department" + ] + if item[ + "employee_objective_id__employee_id__employee_work_info__department_id__department" + ] + else "-" + ), + "Job Position": ( + item[ + "employee_objective_id__employee_id__employee_work_info__job_position_id__job_position" + ] + if item[ + "employee_objective_id__employee_id__employee_work_info__job_position_id__job_position" + ] + else "-" + ), + "Job Role": ( + item[ + "employee_objective_id__employee_id__employee_work_info__job_role_id__job_role" + ] + if item[ + "employee_objective_id__employee_id__employee_work_info__job_role_id__job_role" + ] + else "-" + ), + "Employee Keyresult": item["key_result"], + "Objective": item["employee_objective_id__objective_id__title"], + "Objective Duration": f'{item["employee_objective_id__objective_id__duration"]} {DURATION_UNIT.get(item["employee_objective_id__objective_id__duration_unit"])}', + "Keyresult Start Value": f'{item["start_value"]} {KEY_RESULT_TARGET.get(item["progress_type"])}', + "Keyresult Target Value": f'{item["target_value"]} {KEY_RESULT_TARGET.get(item["progress_type"])}', + "Keyresult Current Value": ( + f'{item["current_value"]} {KEY_RESULT_TARGET.get(item["progress_type"])}' + if item["current_value"] + else "-" + ), + "Keyresult Start Date": ( + item["start_date"] if item["start_date"] else "-" + ), + "Keyresult End Date": item["end_date"] if item["end_date"] else "-", + "status": item["status"], + } + for item in data ] else: - data_list =[] + data_list = [] - return JsonResponse(data_list, safe = False) + return JsonResponse(data_list, safe=False) diff --git a/report/views/recruitment_report.py b/report/views/recruitment_report.py index b9d557845..1dc90129b 100644 --- a/report/views/recruitment_report.py +++ b/report/views/recruitment_report.py @@ -1,6 +1,6 @@ +from django.apps import apps from django.http import JsonResponse from django.shortcuts import render -from django.apps import apps if apps.is_installed("recruitment"): @@ -14,15 +14,24 @@ if apps.is_installed("recruitment"): @login_required @permission_required(perm="recruitment.view_recruitment") def recruitment_report(request): - company = 'all' + company = "all" selected_company = request.session.get("selected_company") - if selected_company != 'all': + if selected_company != "all": company = Company.objects.filter(id=selected_company).first() - return render(request, "report/recruitment_report.html",{'company':company, "f":CandidateFilter(), "fr":RecruitmentFilter(),"fo":OnboardingStageFilter()}) + return render( + request, + "report/recruitment_report.html", + { + "company": company, + "f": CandidateFilter(), + "fr": RecruitmentFilter(), + "fo": OnboardingStageFilter(), + }, + ) @login_required @permission_required(perm="recruitment.view_recruitment") - def recruitment_pivot(request): + def recruitment_pivot(request): model_type = request.GET.get("model", "candidate") # Default to Candidate if model_type == "candidate": @@ -30,97 +39,157 @@ if apps.is_installed("recruitment"): filter_obj = CandidateFilter(request.GET, queryset=qs) qs = filter_obj.qs - data = list(qs.values( - "name","recruitment_id__title","job_position_id__job_position","stage_id__stage","email","mobile", - "gender","offer_letter_status","recruitment_id__closed","recruitment_id__vacancy","country", - "recruitment_id__company_id__company","address","dob","state","city","source","job_position_id__department_id__department" - )) + data = list( + qs.values( + "name", + "recruitment_id__title", + "job_position_id__job_position", + "stage_id__stage", + "email", + "mobile", + "gender", + "offer_letter_status", + "recruitment_id__closed", + "recruitment_id__vacancy", + "country", + "recruitment_id__company_id__company", + "address", + "dob", + "state", + "city", + "source", + "job_position_id__department_id__department", + ) + ) choice_gender = { "male": "Male", "female": "Female", "other": "Other", } OFFER_LETTER_STATUS = { - "not_sent" : "Not Sent", - "sent" : "Sent", - "accepted" : "Accepted", - "rejected" : "Rejected", - "joined" : "Joined", + "not_sent": "Not Sent", + "sent": "Sent", + "accepted": "Accepted", + "rejected": "Rejected", + "joined": "Joined", } SOURCE_CHOICE = { - "application":"Application Form", - "software":"Inside Software", - "other":"Other", + "application": "Application Form", + "software": "Inside Software", + "other": "Other", } - data_list = [ { - "Candidate":item["name"], - "Email":item["email"], - "Phone":item["mobile"], - "Gender":choice_gender.get(item["gender"]), - "Address":item["address"], - "Date Of Birth":item["dob"], - "Country":item["country"] if item["country"] else "-", - "State":item["state"] if item["state"] else "-", - "City":item["city"] if item["city"] else "-", - "Source":SOURCE_CHOICE.get(item["source"]) if item["source"] else "-", - "Job Position":item["job_position_id__job_position"], - "Department":item["job_position_id__department_id__department"], - "Offer Letter":OFFER_LETTER_STATUS.get(item["offer_letter_status"]), - "Recruitment":item["recruitment_id__title"], - "Current Stage":item["stage_id__stage"], - "Recruitment Status": 'Closed' if item["recruitment_id__closed"] else 'Open', - "Vacancy" : item["recruitment_id__vacancy"], - "Company" : item["recruitment_id__company_id__company"], - - - }for item in data + "Candidate": item["name"], + "Email": item["email"], + "Phone": item["mobile"], + "Gender": choice_gender.get(item["gender"]), + "Address": item["address"], + "Date Of Birth": item["dob"], + "Country": item["country"] if item["country"] else "-", + "State": item["state"] if item["state"] else "-", + "City": item["city"] if item["city"] else "-", + "Source": ( + SOURCE_CHOICE.get(item["source"]) if item["source"] else "-" + ), + "Job Position": item["job_position_id__job_position"], + "Department": item["job_position_id__department_id__department"], + "Offer Letter": OFFER_LETTER_STATUS.get( + item["offer_letter_status"] + ), + "Recruitment": item["recruitment_id__title"], + "Current Stage": item["stage_id__stage"], + "Recruitment Status": ( + "Closed" if item["recruitment_id__closed"] else "Open" + ), + "Vacancy": item["recruitment_id__vacancy"], + "Company": item["recruitment_id__company_id__company"], + } + for item in data ] elif model_type == "recruitment": qs = Recruitment.objects.all() - filter_obj = RecruitmentFilter(request.GET, queryset = qs) + filter_obj = RecruitmentFilter(request.GET, queryset=qs) qs = filter_obj.qs - data = list(qs.values( - "title","vacancy","closed","open_positions__job_position","start_date","end_date","is_published", - "recruitment_managers__employee_first_name","recruitment_managers__employee_last_name","company_id__company", - )) + data = list( + qs.values( + "title", + "vacancy", + "closed", + "open_positions__job_position", + "start_date", + "end_date", + "is_published", + "recruitment_managers__employee_first_name", + "recruitment_managers__employee_last_name", + "company_id__company", + ) + ) data_list = [ { - "Recruitment" : item["title"], + "Recruitment": item["title"], "Manager": f"{item['recruitment_managers__employee_first_name']} {item['recruitment_managers__employee_last_name']}", - "Is Closed": 'Closed' if item["closed"] else 'Open', - "Status": 'Published' if item["is_published"] else 'Not Published', - "Start Date":item["start_date"], - "End Date":item["end_date"], - "Job Position":item["open_positions__job_position"], - "Vacancy":item["vacancy"], - "Company" : item["company_id__company"], - }for item in data + "Is Closed": "Closed" if item["closed"] else "Open", + "Status": "Published" if item["is_published"] else "Not Published", + "Start Date": item["start_date"], + "End Date": item["end_date"], + "Job Position": item["open_positions__job_position"], + "Vacancy": item["vacancy"], + "Company": item["company_id__company"], + } + for item in data ] elif model_type == "onboarding": qs = OnboardingStage.objects.all() - filter_obj = OnboardingStageFilter(request.GET, queryset = qs) + filter_obj = OnboardingStageFilter(request.GET, queryset=qs) qs = filter_obj.qs - data = list(qs.values( - "stage_title","recruitment_id__title","employee_id__employee_first_name","employee_id__employee_last_name", - "onboarding_task__task_title", - "onboarding_task__employee_id__employee_first_name","onboarding_task__employee_id__employee_last_name", - "onboarding_task__candidates__name","recruitment_id__company_id__company", - )) + data = list( + qs.values( + "stage_title", + "recruitment_id__title", + "employee_id__employee_first_name", + "employee_id__employee_last_name", + "onboarding_task__task_title", + "onboarding_task__employee_id__employee_first_name", + "onboarding_task__employee_id__employee_last_name", + "onboarding_task__candidates__name", + "recruitment_id__company_id__company", + ) + ) data_list = [ { "Recruitment": item["recruitment_id__title"], "Stage": item["stage_title"], - "Stage Manager": f"{item['employee_id__employee_first_name']} {item['employee_id__employee_last_name']}" if item['employee_id__employee_first_name'] else "-", - "Task": item["onboarding_task__task_title"] if item["onboarding_task__task_title"] else "-", - "Task Manager": f"{item['onboarding_task__employee_id__employee_first_name']} {item['onboarding_task__employee_id__employee_last_name']}" if item['onboarding_task__employee_id__employee_first_name'] else "-", - "Candidates": item["onboarding_task__candidates__name"] if item["onboarding_task__candidates__name"] else "-", - "Company" : item["recruitment_id__company_id__company"] if item["recruitment_id__company_id__company"] else "-", - }for item in data + "Stage Manager": ( + f"{item['employee_id__employee_first_name']} {item['employee_id__employee_last_name']}" + if item["employee_id__employee_first_name"] + else "-" + ), + "Task": ( + item["onboarding_task__task_title"] + if item["onboarding_task__task_title"] + else "-" + ), + "Task Manager": ( + f"{item['onboarding_task__employee_id__employee_first_name']} {item['onboarding_task__employee_id__employee_last_name']}" + if item["onboarding_task__employee_id__employee_first_name"] + else "-" + ), + "Candidates": ( + item["onboarding_task__candidates__name"] + if item["onboarding_task__candidates__name"] + else "-" + ), + "Company": ( + item["recruitment_id__company_id__company"] + if item["recruitment_id__company_id__company"] + else "-" + ), + } + for item in data ] else: data_list = [] diff --git a/static/build/css/pivottable.min.css b/static/build/css/pivottable.min.css index 5237d272c..95ce89a18 100644 --- a/static/build/css/pivottable.min.css +++ b/static/build/css/pivottable.min.css @@ -1,2 +1 @@ .pvtUi{color:#333}table.pvtTable{font-size:8pt;text-align:left;border-collapse:collapse}table.pvtTable tbody tr th,table.pvtTable thead tr th{background-color:#e6EEEE;border:1px solid #CDCDCD;font-size:8pt;padding:5px}table.pvtTable .pvtColLabel{text-align:center}table.pvtTable .pvtTotalLabel{text-align:right}table.pvtTable tbody tr td{color:#3D3D3D;padding:5px;background-color:#FFF;border:1px solid #CDCDCD;vertical-align:top;text-align:right}.pvtGrandTotal,.pvtTotal{font-weight:700}.pvtVals{text-align:center;white-space:nowrap}.pvtColOrder,.pvtRowOrder{cursor:pointer;width:15px;margin-left:5px;display:inline-block}.pvtAggregator{margin-bottom:5px}.pvtAxisContainer,.pvtVals{border:1px solid gray;background:#EEE;padding:5px;min-width:20px;min-height:20px;user-select:none;-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-ms-user-select:none}.pvtAxisContainer li{padding:8px 6px;list-style-type:none;cursor:move}.pvtAxisContainer li.pvtPlaceholder{-webkit-border-radius:5px;padding:3px 15px;-moz-border-radius:5px;border-radius:5px;border:1px dashed #aaa}.pvtAxisContainer li span.pvtAttr{-webkit-text-size-adjust:100%;background:#F3F3F3;border:1px solid #DEDEDE;padding:2px 5px;white-space:nowrap;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.pvtTriangle{cursor:pointer;color:grey}.pvtHorizList li{display:inline}.pvtVertList{vertical-align:top}.pvtFilteredAttribute{font-style:italic}.pvtFilterBox{z-index:100;width:300px;border:1px solid gray;background-color:#fff;position:absolute;text-align:center}.pvtFilterBox h4{margin:15px}.pvtFilterBox p{margin:10px auto}.pvtFilterBox label{font-weight:400}.pvtFilterBox input[type=checkbox]{margin-right:10px;margin-left:10px}.pvtFilterBox input[type=text]{width:230px}.pvtFilterBox .count{color:gray;font-weight:400;margin-left:3px}.pvtCheckContainer{text-align:left;font-size:14px;white-space:nowrap;overflow-y:scroll;width:100%;max-height:250px;border-top:1px solid #d3d3d3;border-bottom:1px solid #d3d3d3}.pvtCheckContainer p{margin:5px}.pvtRendererArea{padding:5px} - diff --git a/static/build/js/pivottable.min.js b/static/build/js/pivottable.min.js index 0537a21e6..b79a65a08 100644 --- a/static/build/js/pivottable.min.js +++ b/static/build/js/pivottable.min.js @@ -1,2 +1,2 @@ (function(){var t,e=[].indexOf||function(t){for(var e=0,n=this.length;e1?n+a[1]:"",r=/(\d+)(\d{3})/;r.test(o);)o=o.replace(r,"$1"+e+"$2");return o+i},m=function(e){var n;return n={digitsAfterDecimal:2,scaler:1,thousandsSep:",",decimalSep:".",prefix:"",suffix:""},e=t.extend({},n,e),function(t){var n;return isNaN(t)||!isFinite(t)?"":(n=i((e.scaler*t).toFixed(e.digitsAfterDecimal),e.thousandsSep,e.decimalSep),""+e.prefix+n+e.suffix)}},A=m(),x=m({digitsAfterDecimal:0}),S=m({digitsAfterDecimal:1,scaler:100,suffix:"%"}),l={count:function(t){return null==t&&(t=x),function(){return function(e,n,r){return{count:0,push:function(){return this.count++},value:function(){return this.count},format:t}}}},uniques:function(t,n){return null==n&&(n=x),function(r){var a;return a=r[0],function(r,o,i){return{uniq:[],push:function(t){var n;if(n=t[a],e.call(this.uniq,n)<0)return this.uniq.push(t[a])},value:function(){return t(this.uniq)},format:n,numInputs:null!=a?0:1}}}},sum:function(t){return null==t&&(t=A),function(e){var n;return n=e[0],function(e,r,a){return{sum:0,push:function(t){if(!isNaN(parseFloat(t[n])))return this.sum+=parseFloat(t[n])},value:function(){return this.sum},format:t,numInputs:null!=n?0:1}}}},extremes:function(t,e){return null==e&&(e=A),function(n){var r;return r=n[0],function(n,a,o){return{val:null,sorter:h(null!=n?n.sorters:void 0,r),push:function(e){var n,a,o,i;if(i=e[r],"min"!==t&&"max"!==t||(i=parseFloat(i),isNaN(i)||(this.val=Math[t](i,null!=(n=this.val)?n:i))),"first"===t&&this.sorter(i,null!=(a=this.val)?a:i)<=0&&(this.val=i),"last"===t&&this.sorter(i,null!=(o=this.val)?o:i)>=0)return this.val=i},value:function(){return this.val},format:function(t){return isNaN(t)?t:e(t)},numInputs:null!=r?0:1}}}},quantile:function(t,e){return null==e&&(e=A),function(n){var r;return r=n[0],function(n,a,o){return{vals:[],push:function(t){var e;if(e=parseFloat(t[r]),!isNaN(e))return this.vals.push(e)},value:function(){var e;return 0===this.vals.length?null:(this.vals.sort(function(t,e){return t-e}),e=(this.vals.length-1)*t,(this.vals[Math.floor(e)]+this.vals[Math.ceil(e)])/2)},format:e,numInputs:null!=r?0:1}}}},runningStat:function(t,e,n){return null==t&&(t="mean"),null==e&&(e=1),null==n&&(n=A),function(r){var a;return a=r[0],function(r,o,i){return{n:0,m:0,s:0,push:function(t){var e,n;if(n=parseFloat(t[a]),!isNaN(n))return this.n+=1,1===this.n?this.m=n:(e=this.m+(n-this.m)/this.n,this.s=this.s+(n-this.m)*(n-e),this.m=e)},value:function(){if("mean"===t)return 0===this.n?NaN:this.m;if(this.n<=e)return 0;switch(t){case"var":return this.s/(this.n-e);case"stdev":return Math.sqrt(this.s/(this.n-e))}},format:n,numInputs:null!=a?0:1}}}},sumOverSum:function(t){return null==t&&(t=A),function(e){var n,r;return r=e[0],n=e[1],function(e,a,o){return{sumNum:0,sumDenom:0,push:function(t){if(isNaN(parseFloat(t[r]))||(this.sumNum+=parseFloat(t[r])),!isNaN(parseFloat(t[n])))return this.sumDenom+=parseFloat(t[n])},value:function(){return this.sumNum/this.sumDenom},format:t,numInputs:null!=r&&null!=n?0:2}}}},sumOverSumBound80:function(t,e){return null==t&&(t=!0),null==e&&(e=A),function(n){var r,a;return a=n[0],r=n[1],function(n,o,i){return{sumNum:0,sumDenom:0,push:function(t){if(isNaN(parseFloat(t[a]))||(this.sumNum+=parseFloat(t[a])),!isNaN(parseFloat(t[r])))return this.sumDenom+=parseFloat(t[r])},value:function(){var e;return e=t?1:-1,(.821187207574908/this.sumDenom+this.sumNum/this.sumDenom+1.2815515655446004*e*Math.sqrt(.410593603787454/(this.sumDenom*this.sumDenom)+this.sumNum*(1-this.sumNum/this.sumDenom)/(this.sumDenom*this.sumDenom)))/(1+1.642374415149816/this.sumDenom)},format:e,numInputs:null!=a&&null!=r?0:2}}}},fractionOf:function(t,e,r){return null==e&&(e="total"),null==r&&(r=S),function(){var a;return a=1<=arguments.length?n.call(arguments,0):[],function(n,o,i){return{selector:{total:[[],[]],row:[o,[]],col:[[],i]}[e],inner:t.apply(null,a)(n,o,i),push:function(t){return this.inner.push(t)},format:r,value:function(){return this.inner.value()/n.getAggregator.apply(n,this.selector).inner.value()},numInputs:t.apply(null,a)().numInputs}}}}},l.countUnique=function(t){return l.uniques(function(t){return t.length},t)},l.listUnique=function(t){return l.uniques(function(e){return e.sort(f).join(t)},function(t){return t})},l.max=function(t){return l.extremes("max",t)},l.min=function(t){return l.extremes("min",t)},l.first=function(t){return l.extremes("first",t)},l.last=function(t){return l.extremes("last",t)},l.median=function(t){return l.quantile(.5,t)},l.average=function(t){return l.runningStat("mean",1,t)},l["var"]=function(t,e){return l.runningStat("var",t,e)},l.stdev=function(t,e){return l.runningStat("stdev",t,e)},s=function(t){return{Count:t.count(x),"Count Unique Values":t.countUnique(x),"List Unique Values":t.listUnique(", "),Sum:t.sum(A),"Integer Sum":t.sum(x),Average:t.average(A),Median:t.median(A),"Sample Variance":t["var"](1,A),"Sample Standard Deviation":t.stdev(1,A),Minimum:t.min(A),Maximum:t.max(A),First:t.first(A),Last:t.last(A),"Sum over Sum":t.sumOverSum(A),"80% Upper Bound":t.sumOverSumBound80(!0,A),"80% Lower Bound":t.sumOverSumBound80(!1,A),"Sum as Fraction of Total":t.fractionOf(t.sum(),"total",S),"Sum as Fraction of Rows":t.fractionOf(t.sum(),"row",S),"Sum as Fraction of Columns":t.fractionOf(t.sum(),"col",S),"Count as Fraction of Total":t.fractionOf(t.count(),"total",S),"Count as Fraction of Rows":t.fractionOf(t.count(),"row",S),"Count as Fraction of Columns":t.fractionOf(t.count(),"col",S)}}(l),b={Table:function(t,e){return g(t,e)},"Table Barchart":function(e,n){return t(g(e,n)).barchart()},Heatmap:function(e,n){return t(g(e,n)).heatmap("heatmap",n)},"Row Heatmap":function(e,n){return t(g(e,n)).heatmap("rowheatmap",n)},"Col Heatmap":function(e,n){return t(g(e,n)).heatmap("colheatmap",n)}},d={en:{aggregators:s,renderers:b,localeStrings:{renderError:"An error occurred rendering the PivotTable results.",computeError:"An error occurred computing the PivotTable results.",uiRenderError:"An error occurred rendering the PivotTable UI.",selectAll:"Select All",selectNone:"Select None",tooMany:"(too many to list)",filterResults:"Filter values",apply:"Apply",cancel:"Cancel",totals:"Totals",vs:"vs",by:"by"}}},p=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],u=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],N=function(t){return("0"+t).substr(-2,2)},c={bin:function(t,e){return function(n){return n[t]-n[t]%e}},dateFormat:function(t,e,n,r,a){var o;return null==n&&(n=!1),null==r&&(r=p),null==a&&(a=u),o=n?"UTC":"",function(n){var i;return i=new Date(Date.parse(n[t])),isNaN(i)?"":e.replace(/%(.)/g,function(t,e){switch(e){case"y":return i["get"+o+"FullYear"]();case"m":return N(i["get"+o+"Month"]()+1);case"n":return r[i["get"+o+"Month"]()];case"d":return N(i["get"+o+"Date"]());case"w":return a[i["get"+o+"Day"]()];case"x":return i["get"+o+"Day"]();case"H":return N(i["get"+o+"Hours"]());case"M":return N(i["get"+o+"Minutes"]());case"S":return N(i["get"+o+"Seconds"]());default:return"%"+e}})}}},C=/(\d+)|(\D+)/g,v=/\d/,y=/^0/,f=function(t){return function(t,e){var n,r,a,o,i,l;if(null!=e&&null==t)return-1;if(null!=t&&null==e)return 1;if("number"==typeof t&&isNaN(t))return-1;if("number"==typeof e&&isNaN(e))return 1;if(i=+t,l=+e,il)return 1;if("number"==typeof t&&"number"!=typeof e)return-1;if("number"==typeof e&&"number"!=typeof t)return 1;if("number"==typeof t&&"number"==typeof e)return 0;if(isNaN(l)&&!isNaN(i))return-1;if(isNaN(i)&&!isNaN(l))return 1;if(n=String(t),a=String(e),n===a)return 0;if(!v.test(n)||!v.test(a))return n>a?1:-1;for(n=n.match(C),a=a.match(C);n.length&&a.length;)if(r=n.shift(),o=a.shift(),r!==o)return v.test(r)&&v.test(o)?r.replace(y,".0")-o.replace(y,".0"):r>o?1:-1;return n.length-a.length}}(this),w=function(t){var e,n,r,a;r={},n={};for(e in t)a=t[e],r[a]=e,"string"==typeof a&&(n[a.toLowerCase()]=e);return function(t,e){return null!=r[t]&&null!=r[e]?r[t]-r[e]:null!=r[t]?-1:null!=r[e]?1:null!=n[t]&&null!=n[e]?n[t]-n[e]:null!=n[t]?-1:null!=n[e]?1:f(t,e)}},h=function(e,n){var r;if(null!=e)if(t.isFunction(e)){if(r=e(n),t.isFunction(r))return r}else if(null!=e[n])return e[n];return f},o=function(){function e(t,n){var a,o,i,s,u,c,h,d,p,f;null==n&&(n={}),this.getAggregator=r(this.getAggregator,this),this.getRowKeys=r(this.getRowKeys,this),this.getColKeys=r(this.getColKeys,this),this.sortKeys=r(this.sortKeys,this),this.arrSort=r(this.arrSort,this),this.input=t,this.aggregator=null!=(a=n.aggregator)?a:l.count()(),this.aggregatorName=null!=(o=n.aggregatorName)?o:"Count",this.colAttrs=null!=(i=n.cols)?i:[],this.rowAttrs=null!=(s=n.rows)?s:[],this.valAttrs=null!=(u=n.vals)?u:[],this.sorters=null!=(c=n.sorters)?c:{},this.rowOrder=null!=(h=n.rowOrder)?h:"key_a_to_z",this.colOrder=null!=(d=n.colOrder)?d:"key_a_to_z",this.derivedAttributes=null!=(p=n.derivedAttributes)?p:{},this.filter=null!=(f=n.filter)?f:function(){return!0},this.tree={},this.rowKeys=[],this.colKeys=[],this.rowTotals={},this.colTotals={},this.allTotal=this.aggregator(this,[],[]),this.sorted=!1,e.forEachRecord(this.input,this.derivedAttributes,function(t){return function(e){if(t.filter(e))return t.processRecord(e)}}(this))}return e.forEachRecord=function(e,n,r){var o,i,l,s,u,c,h,d,p,f,m,g;if(o=t.isEmptyObject(n)?r:function(t){var e,a,o;for(e in n)o=n[e],t[e]=null!=(a=o(t))?a:t[e];return r(t)},t.isFunction(e))return e(o);if(t.isArray(e)){if(t.isArray(e[0])){f=[];for(l in e)if(a.call(e,l)&&(i=e[l],l>0)){d={},p=e[0];for(s in p)a.call(p,s)&&(u=p[s],d[u]=i[s]);f.push(o(d))}return f}for(m=[],c=0,h=e.length;c tr > th",e).each(function(e){return g.push(t(this).text())}),t("tbody > tr",e).each(function(e){return d={},t("td",this).each(function(e){return d[g[e]]=t(this).text()}),o(d)});throw new Error("unknown input format")},e.prototype.forEachMatchingRecord=function(t,n){return e.forEachRecord(this.input,this.derivedAttributes,function(e){return function(r){var a,o,i;if(e.filter(r)){for(a in t)if(i=t[a],i!==(null!=(o=r[a])?o:"null"))return;return n(r)}}}(this))},e.prototype.arrSort=function(t){var e,n;return n=function(){var n,r,a;for(a=[],n=0,r=t.length;n=l;c=0<=l?++r:--r)t[e-1][c]!==t[e][c]&&(i=!1);if(i)return-1}for(a=0;e+a=s;c=0<=s?++o:--o)t[e][c]!==t[e+a][c]&&(u=!0);if(u)break;a++}return a},A=document.createElement("thead");for(d in i)if(a.call(i,d)){o=i[d],S=document.createElement("tr"),0===parseInt(d)&&0!==m.length&&(w=document.createElement("th"),w.setAttribute("colspan",m.length),w.setAttribute("rowspan",i.length),S.appendChild(w)),w=document.createElement("th"),w.className="pvtAxisLabel",w.textContent=o,S.appendChild(w);for(h in s)a.call(s,h)&&(l=s[h],k=b(s,parseInt(h),parseInt(d)),k!==-1&&(w=document.createElement("th"),w.className="pvtColLabel",w.textContent=l[d],w.setAttribute("colspan",k),parseInt(d)===i.length-1&&0!==m.length&&w.setAttribute("rowspan",2),S.appendChild(w)));0===parseInt(d)&&n.table.rowTotals&&(w=document.createElement("th"),w.className="pvtTotalLabel pvtRowTotalLabel",w.innerHTML=n.localeStrings.totals,w.setAttribute("rowspan",i.length+(0===m.length?0:1)),S.appendChild(w)),A.appendChild(S)}if(0!==m.length){S=document.createElement("tr");for(h in m)a.call(m,h)&&(p=m[h],w=document.createElement("th"),w.className="pvtAxisLabel",w.textContent=p,S.appendChild(w));w=document.createElement("th"),0===i.length&&(w.className="pvtTotalLabel pvtRowTotalLabel",w.innerHTML=n.localeStrings.totals),S.appendChild(w),A.appendChild(S)}f.appendChild(A),C=document.createElement("tbody");for(h in v)if(a.call(v,h)){g=v[h],S=document.createElement("tr");for(d in g)a.call(g,d)&&(N=g[d],k=b(v,parseInt(h),parseInt(d)),k!==-1&&(w=document.createElement("th"),w.className="pvtRowLabel",w.textContent=N,w.setAttribute("rowspan",k),parseInt(d)===m.length-1&&0!==i.length&&w.setAttribute("colspan",2),S.appendChild(w)));for(d in s)a.call(s,d)&&(l=s[d],r=e.getAggregator(g,l),T=r.value(),y=document.createElement("td"),y.className="pvtVal row"+h+" col"+d,y.textContent=r.format(T),y.setAttribute("data-value",T),null!=c&&(y.onclick=c(T,g,l)),S.appendChild(y));(n.table.rowTotals||0===i.length)&&(x=e.getAggregator(g,[]),T=x.value(),y=document.createElement("td"),y.className="pvtTotal rowTotal",y.textContent=x.format(T),y.setAttribute("data-value",T),null!=c&&(y.onclick=c(T,g,[])),y.setAttribute("data-for","row"+h),S.appendChild(y)),C.appendChild(S)}if(n.table.colTotals||0===m.length){S=document.createElement("tr"),(n.table.colTotals||0===m.length)&&(w=document.createElement("th"),w.className="pvtTotalLabel pvtColTotalLabel",w.innerHTML=n.localeStrings.totals,w.setAttribute("colspan",m.length+(0===i.length?0:1)),S.appendChild(w));for(d in s)a.call(s,d)&&(l=s[d],x=e.getAggregator([],l),T=x.value(),y=document.createElement("td"),y.className="pvtTotal colTotal",y.textContent=x.format(T),y.setAttribute("data-value",T),null!=c&&(y.onclick=c(T,[],l)),y.setAttribute("data-for","col"+d),S.appendChild(y));(n.table.rowTotals||0===i.length)&&(x=e.getAggregator([],[]),T=x.value(),y=document.createElement("td"),y.className="pvtGrandTotal",y.textContent=x.format(T),y.setAttribute("data-value",T),null!=c&&(y.onclick=c(T,[],[])),S.appendChild(y)),C.appendChild(S)}return f.appendChild(C),f.setAttribute("data-numrows",v.length),f.setAttribute("data-numcols",s.length),f},t.fn.pivot=function(e,n,r){var a,i,s,u,c,h,p,f;null==r&&(r="en"),null==d[r]&&(r="en"),a={cols:[],rows:[],vals:[],rowOrder:"key_a_to_z",colOrder:"key_a_to_z",dataClass:o,filter:function(){return!0},aggregator:l.count()(),aggregatorName:"Count",sorters:{},derivedAttributes:{},renderer:g},u=t.extend(!0,{},d.en.localeStrings,d[r].localeStrings),s={rendererOptions:{localeStrings:u},localeStrings:u},c=t.extend(!0,{},s,t.extend({},a,n)),p=null;try{h=new c.dataClass(e,c);try{p=c.renderer(h,c.rendererOptions)}catch(m){i=m,"undefined"!=typeof console&&null!==console&&console.error(i.stack),p=t("").html(c.localeStrings.renderError)}}catch(m){i=m,"undefined"!=typeof console&&null!==console&&console.error(i.stack),p=t("").html(c.localeStrings.computeError)}for(f=this[0];f.hasChildNodes();)f.removeChild(f.lastChild);return this.append(p)},t.fn.pivotUI=function(n,r,i,l){var s,u,c,p,m,g,v,b,C,y,w,A,x,S,N,T,k,O,_,F,D,E,M,R,I,L,U,K,q,z,V,j,H,B,P,J,G,W,$,Q,Y,X,Z,tt,et;null==i&&(i=!1),null==l&&(l="en"),null==d[l]&&(l="en"),b={derivedAttributes:{},aggregators:d[l].aggregators,renderers:d[l].renderers,hiddenAttributes:[],hiddenFromAggregators:[],hiddenFromDragDrop:[],menuLimit:500,cols:[],rows:[],vals:[],rowOrder:"key_a_to_z",colOrder:"key_a_to_z",dataClass:o,exclusions:{},inclusions:{},unusedAttrsVertical:85,autoSortUnusedAttrs:!1,onRefresh:null,showUI:!0,filter:function(){return!0},sorters:{}},_=t.extend(!0,{},d.en.localeStrings,d[l].localeStrings),O={rendererOptions:{localeStrings:_},localeStrings:_},y=this.data("pivotUIOptions"),M=null==y||i?t.extend(!0,{},O,t.extend({},b,r)):y;try{m={},F=[],L=0,o.forEachRecord(n,M.derivedAttributes,function(t){var e,n,r,o;if(M.filter(t)){F.push(t);for(e in t)a.call(t,e)&&null==m[e]&&(m[e]={},L>0&&(m[e]["null"]=L));for(e in m)o=null!=(r=t[e])?r:"null",null==(n=m[e])[o]&&(n[o]=0),m[e][o]++;return L++}}),Y=t("",{"class":"pvtUi"}).attr("cellpadding",5),B=t("").appendTo(Y),u=t("").appendTo(Y),Q.append(t("").append(B).append(X)),this.html(Y),q=M.cols,D=0,T=q.length;Dp;et=0<=p?++g:--g){for(i=t("
").addClass("pvtUiCell"),H=t("").addClass("pvtAxisContainer pvtUnused pvtUiCell"),J=function(){var t;t=[];for(s in m)e.call(M.hiddenAttributes,s)<0&&t.push(s);return t}(),G=function(){var t,n,r;for(r=[],t=0,n=J.length;tZ}M.unusedAttrsVertical===!0||tt?X.addClass("pvtVertList"):X.addClass("pvtHorizList"),w=function(n){var r,a,o,i,l,s,u,c,d,p,f,g,v,b,C,y,w,x,S;if(S=function(){var t;t=[];for(C in m[n])t.push(C);return t}(),c=!1,x=t("
").addClass("pvtFilterBox").hide(),x.append(t("

").append(t("").text(n),t("").addClass("count").text("("+S.length+")"))),S.length>M.menuLimit)x.append(t("

").html(M.localeStrings.tooMany));else for(S.length>5&&(i=t("

").appendTo(x),v=h(M.sorters,n),f=M.localeStrings.filterResults,t("",{type:"text"}).appendTo(i).attr({placeholder:f,"class":"pvtSearch"}).bind("keyup",function(){var n,r,a;return a=t(this).val().toLowerCase().trim(),r=function(t,n){return function(r){var o,i;return o=a.substring(t.length).trim(),0===o.length||(i=Math.sign(v(r.toLowerCase(),o)),e.call(n,i)>=0)}},n=0===a.indexOf(">=")?r(">=",[1,0]):0===a.indexOf("<=")?r("<=",[-1,0]):0===a.indexOf(">")?r(">",[1]):0===a.indexOf("<")?r("<",[-1]):0===a.indexOf("~")?function(t){return 0===a.substring(1).trim().length||t.toLowerCase().match(a.substring(1))}:function(t){return t.toLowerCase().indexOf(a)!==-1},x.find(".pvtCheckContainer p label span.value").each(function(){return n(t(this).text())?t(this).parent().parent().show():t(this).parent().parent().hide()})}),i.append(t("
")),t("

").addClass("pvtVals pvtUiCell").appendTo($).append(u).append(P).append(v).append(t("
")),t("
").addClass("pvtAxisContainer pvtHorizList pvtCols pvtUiCell").appendTo($),Q=t("
").addClass("pvtAxisContainer pvtRows pvtUiCell").attr("valign","top")),I=t("").attr("valign","top").addClass("pvtRendererArea").appendTo(Q),M.unusedAttrsVertical===!0||tt?(Y.find("tr:nth-child(1)").prepend(B),Y.find("tr:nth-child(2)").prepend(X)):Y.prepend(t("