diff --git a/custom_addons/customer_orders/static/src/js/dashboard.js b/custom_addons/customer_orders/static/src/js/dashboard.js index e90337a23..216f5a4c7 100644 --- a/custom_addons/customer_orders/static/src/js/dashboard.js +++ b/custom_addons/customer_orders/static/src/js/dashboard.js @@ -3,6 +3,7 @@ import { useService } from "@web/core/utils/hooks"; import { standardActionServiceProps } from "@web/webclient/actions/action_service"; import { registry } from "@web/core/registry"; import { Component, onMounted, useRef, onWillStart, useState } from "@odoo/owl"; +import { loadCSS } from '@web/core/assets'; @@ -18,6 +19,11 @@ export class CustomerOrderStatusGrid extends Component { this.notification = useService("notification"); this.action = useService("action"); + const currentDate = new Date(); + const currentMonth = String(currentDate.getMonth() + 1).padStart(2, '0'); + const currentYear = currentDate.getFullYear(); + const currentMonthYear = `${currentYear}-${currentMonth}`; + this.state = useState({ // Active tab activeTab: 'order', @@ -63,7 +69,8 @@ export class CustomerOrderStatusGrid extends Component { totalOrderQty: 0, totalFgAvailable: 0, totalRemainingProduction: 0 - } + }, + selectedMonthYear:currentMonthYear }); this.orderGridRef = useRef('orderGrid'); @@ -73,6 +80,7 @@ export class CustomerOrderStatusGrid extends Component { onWillStart(async () => { await this.loadGridData(); +// await loadCSS('/web_grid/static/lib/pq_grid/themes/gray/pqgrid.css') }); @@ -82,6 +90,26 @@ export class CustomerOrderStatusGrid extends Component { }); } + onMonthYearChange(event) { + this.state.selectedMonthYear = event.target.value; + } + + clearFilter() { + this.state.selectedMonthYear = ''; + this.loadGridData(); + this.notification.add("Filter cleared", { type: "success" }); + } + +// Helper method to format month/year label + getMonthYearLabel() { + if (!this.state.selectedMonthYear) return ''; + const [year, month] = this.state.selectedMonthYear.split('-'); + const monthNames = [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ]; + return `${monthNames[parseInt(month) - 1]} ${year}`; + } // Helper functions - make them available to template formatFloat(value) { if (value === null || value === undefined || isNaN(value)) { @@ -119,12 +147,21 @@ export class CustomerOrderStatusGrid extends Component { async loadGridData() { this.state.isLoading = true; try { - const data = await this.orm.call( - "cor.dashboard", - "get_customer_order_status_data", - [[]], - {} - ); + let month = ''; + let year = ''; + + // If filter is set, use it + if (this.state.selectedMonthYear) { + [year, month] = this.state.selectedMonthYear.split('-'); + } + + const data = await this.orm.call( + "cor.dashboard", + "get_customer_order_status_data", + [month,year], + {} + ); + let totalOrderQty = 0; let totalProducedQty = 0; @@ -184,10 +221,18 @@ export class CustomerOrderStatusGrid extends Component { async loadDispatchSummary() { this.state.dispatchLoading = true; try { + + let month = ''; + let year = ''; + + // If filter is set, use it + if (this.state.selectedMonthYear) { + [year, month] = this.state.selectedMonthYear.split('-'); + } const data = await this.orm.call( "cor.dashboard", "get_dispatch_summary_data", - [[]], + [month,year], {} ); @@ -198,10 +243,11 @@ export class CustomerOrderStatusGrid extends Component { const processedData = data.map(row => { const orderQty = parseFloat(row.order_qty) || 0; - const dispatchedQty = parseFloat(row.dispatched_qty) || 0; + const dispatchedQty = parseFloat(row.invoiced_qty) || 0; const balanceQty = Math.max(0, orderQty - dispatchedQty); const dispatchStatus = balanceQty === 0 ? "Full" : "Partial"; + totalOrders++; if (dispatchStatus === "Full") { fullDispatch++; @@ -248,10 +294,18 @@ export class CustomerOrderStatusGrid extends Component { async loadRMAvailability() { this.state.rmLoading = true; try { + + let month = ''; + let year = ''; + + // If filter is set, use it + if (this.state.selectedMonthYear) { + [year, month] = this.state.selectedMonthYear.split('-'); + } const data = await this.orm.call( "cor.dashboard", "get_raw_material_availability_data", - [[]], + [year, month], {} ); @@ -411,10 +465,12 @@ export class CustomerOrderStatusGrid extends Component { return; } + $(this.orderGridRef.el).pqGrid({ width: "100%", height: 500, title: "Customer Order Status", + editable:false, dataModel: { data: this.state.gridData, location: "local", @@ -422,15 +478,7 @@ export class CustomerOrderStatusGrid extends Component { paging: "local", rPP: 50 }, - groupModel: { - on: true, - dataIndx: ['customer'], - collapsed: [false], - merge: true, - showSummary: [true], - grandSummary: true, - summaryTitle: 'Subtotal:' - }, + summaryModel: { on: true, type: 'top', @@ -452,53 +500,147 @@ export class CustomerOrderStatusGrid extends Component { }, numberCell: { show: true, title: "#", width: 60 }, freezeCols: 2, - style: { - border: '1px solid #dee2e6', - borderRadius: '4px' - } + }); + + const groupModel = { + on: true, + dataIndx: ['customer'], + collapsed: [false], + merge: true, + showSummary: [true], + grandSummary: true, + summaryTitle: 'Subtotal:' + } + $(this.orderGridRef.el).pqGrid("groupOption", groupModel); } } initializeDispatchGrid() { - if (this.dispatchGridRef.el && this.state.dispatchData.length > 0) { - const columns = this.getDispatchGridColumns(); - - if (typeof window.$ === 'undefined' || typeof window.pq === 'undefined') { - console.warn('jQuery or paramQuery not loaded'); - return; - } - - $(this.dispatchGridRef.el).pqGrid({ - width: "100%", - height: 500, - title: "Dispatch Summary", - dataModel: { - data: this.state.dispatchData, - location: "local", - sorting: "local", - paging: "local", - rPP: 20 - }, - colModel: columns, - numberCell: { show: true, title: "#", width: 40 }, - scrollModel: { autoFit: true }, - freezeCols: 3, - filterModel: { - on: true, - mode: "AND", - header: true, - autoSearch: true, - type: 'local' - }, - style: { - border: '1px solid #dee2e6', - borderRadius: '4px' - } - }); - } + if (!this.dispatchGridRef.el || this.state.dispatchData.length === 0) { + return; } + if (typeof window.$ === 'undefined' || typeof window.pq === 'undefined') { + console.warn('jQuery or paramQuery not loaded'); + return; + } + const agg = pq.aggregate; + agg.sum_ = function(arr, col) { + return " " + agg.sum(arr, col).toFixed(2).toString(); + }; + + // -------------------------------------------------- + // 1. Collect unique invoice dates from query result + // -------------------------------------------------- + const dateSet = new Set(); + + this.state.dispatchData.forEach(row => { + (row.invoice_date_qty || []).forEach(d => { + dateSet.add(d.invoice_date); + }); + }); + + const dateColumns = Array.from(dateSet).sort(); + + // -------------------------------------------------- + // 2. Flatten date-wise quantities into row object + // -------------------------------------------------- + this.state.dispatchData.forEach(row => { + (row.invoice_date_qty || []).forEach(d => { + row[d.invoice_date] = d.qty; + }); + delete row.invoice_date_qty; + }); + + // -------------------------------------------------- + // 3. Base columns + // -------------------------------------------------- + const columns = this.getDispatchGridColumns(); + + // -------------------------------------------------- + // 4. Dynamic date columns from query + // -------------------------------------------------- + dateColumns.forEach(date => { + const [year, month, day] = date.split('-'); + columns.push({ + title: `${day}/${month}/${year}`, // day only + dataIndx: date, + width: 100, + align: "right", + dataType: "float", + summary: { type: "sum_" } + }); + }); + + // -------------------------------------------------- + // 5. Destroy existing grid (important) + // -------------------------------------------------- + const $grid = $(this.dispatchGridRef.el); + if ($grid.data("pqGrid")) { + $grid.pqGrid("destroy"); + } + + // -------------------------------------------------- + // 6. Initialize ParamQuery Grid + // -------------------------------------------------- + $grid.pqGrid({ + width: "100%", + height: 600, + title: "Dispatch Summary", + editable:false, + + dataModel: { + data: this.state.dispatchData, + location: "local", + sorting: "local", + paging: "local", + rPP: 20 + }, + + colModel: columns, + + numberCell: { + show: true, + title: "#", + width: 40 + }, + + freezeCols: 4, + summaryModel: { + on: true, + type: 'top', + title: 'Grand Total:', + col: ['order_qty', 'invoiced_qty'] + }, + + filterModel: { + on: true, + mode: "AND", + header: true, + autoSearch: true, + type: "local" + }, + + style: { + border: "1px solid #dee2e6", + borderRadius: "4px" + } + }); + const groupModel = { + on: true, + dataIndx: ['customer'], + collapsed: [false], + merge: true, + showSummary: [true,false], + grandSummary: true, + summaryTitle: 'Subtotal:' + + } + $grid.pqGrid("groupOption", groupModel); +} + + initializeRMGrid() { if (this.rmGridRef.el && this.state.rmData.length > 0) { const columns = this.getRMGridColumns(); @@ -522,6 +664,7 @@ export class CustomerOrderStatusGrid extends Component { colModel: columns, numberCell: { show: true, title: "#", width: 30 }, scrollModel: { autoFit: true }, + editable:false, freezeCols: 1, filterModel: { on: true, @@ -551,6 +694,7 @@ export class CustomerOrderStatusGrid extends Component { width: "100%", height: 500, title: "Daily Production Report", + editable:false, dataModel: { data: this.state.dprData, location: "local", @@ -786,11 +930,22 @@ export class CustomerOrderStatusGrid extends Component { ]; } + getMonthDates(year, month) { + const dates = []; + const date = new Date(year, month - 1, 1); + + while (date.getMonth() === month - 1) { + dates.push(new Date(date)); + date.setDate(date.getDate() + 1); + } + return dates; + } + getDispatchGridColumns() { return [ { title: "Customer", - width: 120, + width: 220, dataIndx: "customer", dataType: "string", align: "left", @@ -799,7 +954,7 @@ export class CustomerOrderStatusGrid extends Component { }, { title: "Product", - width: 100, + width: 250, dataIndx: "product", dataType: "string", align: "left", @@ -808,53 +963,109 @@ export class CustomerOrderStatusGrid extends Component { }, { title: "Order Qty", - width: 60, - dataIndx: "order_qty", - dataType: "float", - align: "right", - render: (ui) => this.formatInteger(ui.cellData), - frozen: true, - style: { backgroundColor: '#e3f2fd', textAlign: 'right' } + colModel: [ + { + title: "Bags", + width: 90, + dataIndx: "order_qty", + dataType: "float", + filter: true, + sortable: true, + align: "right", + render: (ui) => this.formatInteger(ui.cellData), + summary: { + type: "sum", + label: "Total:", + render: (ui) => this.formatInteger(ui.data) + } + }, + { + title: "Kgs", + width: 90, + dataIndx: "order_qty_kg", + dataType: "float", + filter: true, + sortable: true, + align: "right", + render: (ui) => this.formatFloat(ui.cellData), + summary: { + type: "sum", + label: "Total:", + render: (ui) => this.formatFloat(ui.data) + } + } + ] }, { title: "Dispatched Qty", - width: 60, - dataIndx: "dispatched_qty", - dataType: "float", - align: "right", - render: (ui) => this.formatInteger(ui.cellData), - style: { backgroundColor: '#d4edda', textAlign: 'right' } + colModel: [ + { + title: "Bags", + width: 90, + dataIndx: "invoiced_qty", + dataType: "float", + filter: true, + sortable: true, + align: "right", + render: (ui) => this.formatInteger(ui.cellData), + + summary: { + type: "sum", + label: "Total:", + render: (ui) => this.formatInteger(ui.data) + } + }, + { + title: "Kgs", + width: 90, + dataIndx: "invoiced_qty_kg", + dataType: "float", + filter: true, + sortable: true, + align: "right", + render: (ui) => this.formatFloat(ui.cellData), + + summary: { + type: "sum", + label: "Total:", + render: (ui) => this.formatFloat(ui.data) + } + } + ] }, { title: "Balance Qty", - width: 60, + width: 90, dataIndx: "balance_qty", dataType: "float", align: "right", render: (ui) => this.formatInteger(ui.cellData), - style: (ui) => { - const balance = parseFloat(ui.cellData) || 0; - return { - backgroundColor: balance > 0 ? '#f8d7da' : '#d4edda', - textAlign: 'right' - }; - } + summary: { + type: "sum", + label: "Total:", + render: (ui) => this.formatInteger(ui.data) + } }, { title: "Status", width: 70, + summary: false, dataIndx: "dispatch_status", dataType: "string", align: "center", - render: (ui) => { - const status = ui.cellData; + render: (ui) => { + const status = ui.cellData || ''; // Default to empty string if undefined + if (!status) return ''; // Return empty if no status + const color = status === 'Full' ? '#155724' : '#856404'; return `${status}`; }, - style: (ui) => ({ - backgroundColor: ui.cellData === 'Full' ? '#d4edda' : '#fff3cd', - textAlign: 'center' - }) + attr:ui => ({ + style: { + backgroundColor: ui.cellData > 0 ? '#d4edda' : '', + textAlign: 'right' + } + }) } ]; } @@ -868,7 +1079,8 @@ export class CustomerOrderStatusGrid extends Component { dataType: "string", align: "left", frozen: true, - style: { backgroundColor: '#f0f8ff', fontWeight: 'bold' } + style: { backgroundColor: '#f0f8ff', fontWeight: 'bold' }, + filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] } }, { @@ -909,7 +1121,7 @@ export class CustomerOrderStatusGrid extends Component { width: 60, dataIndx: "uom_name", dataType: "string", - align: "center", + align: "left", style: { textAlign: 'center' } }, { @@ -1096,7 +1308,8 @@ export class CustomerOrderStatusGrid extends Component { return 'badge-danger'; } - exportGrid(type) { + async exportGrid(type) { + let gridRef; let filename; diff --git a/custom_addons/customer_orders/static/src/xml/dashboard.xml b/custom_addons/customer_orders/static/src/xml/dashboard.xml index 3b852d197..f47ea02ba 100644 --- a/custom_addons/customer_orders/static/src/xml/dashboard.xml +++ b/custom_addons/customer_orders/static/src/xml/dashboard.xml @@ -8,7 +8,23 @@
Customer Order Status Dashboard
-
+ +
+
+
+
+ + + + +
+
+
+
+
Total Orders: @@ -366,7 +382,7 @@ } .header-card { - background: linear-gradient(135deg, #fb4d2f 0%, #f494a9 100%); + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 10px; box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); @@ -422,6 +438,7 @@ padding: 5px 10px; min-width: 60px; text-align: center; + color : white; } .refresh-btn { @@ -744,33 +761,77 @@ font-size: 2rem; } + /* Simple Filter Styles - Right side */ +.simple-filter { + margin-top: 15px; + padding: 10px 0; +} + +/* Position filter on right side */ +.col-auto.ms-auto { + margin-left: auto !important; +} + +.input-group-sm { + width: 160px; +} + +.input-group-sm .form-control { + background: white; + border: 1px solid rgba(255, 255, 255, 0.3); + color: #495057; +} + +.input-group-sm .input-group-text { + background: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.3); + color: white; +} + +/* Hover effects */ +.input-group-sm:hover .input-group-text { + background: rgba(255, 255, 255, 0.3); +} + +/* Responsive */ +@media (max-width: 768px) { + .col-auto.ms-auto { + margin-left: 0 !important; + margin-top: 10px; + } + + .simple-filter { + text-align: center; + } +} + /* ParamQuery Grid Customization */ - .pq-grid { - border: none !important; - } + + + - .pq-grid .pq-grid-header { - background: #f8f9fa !important; - border-bottom: 2px solid #dee2e6 !important; - } + + + + - .pq-grid .pq-grid-title { - color: #495057 !important; - font-weight: 600 !important; - } + + + + - .pq-grid .pq-grid-cell { - border-right: 1px solid #e9ecef !important; - border-bottom: 1px solid #e9ecef !important; - } + + + + - .pq-grid .pq-grid-row:nth-child(even) { - background-color: #f8f9fa !important; - } + + + - .pq-grid .pq-grid-row:hover { - background-color: #e9ecef !important; - } + + + \ No newline at end of file