customer orders Module

This commit is contained in:
Raman Marikanti 2026-02-10 12:51:37 +05:30
parent f638146824
commit b1b0396c72
2 changed files with 388 additions and 114 deletions

View File

@ -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 `<span style="color: ${color}; font-weight: bold;">${status}</span>`;
},
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;

View File

@ -8,7 +8,23 @@
<div class="header-card">
<div class="header-content">
<h5 class="header-title">Customer Order Status Dashboard</h5>
<div class="header-stats" t-if="!state.isLoading">
<!-- Simple Month/Year Filter - Auto filter on change -->
<div class="simple-filter" t-if="!state.isLoading">
<div class="row g-2 align-items-center">
<div class="col-auto ms-auto">
<div class="input-group input-group-sm">
<span class="input-group-text">
<i class="fa fa-calendar"></i>
</span>
<input type="month"
class="form-control form-control-sm"
t-model="state.selectedMonthYear"
t-on-change="loadAllData"/>
</div>
</div>
</div>
</div>
<div class="header-stats" t-if="!state.isLoading">
<div class="stats-container">
<div class="stat-item">
<span class="stat-label">Total Orders:</span>
@ -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 {-->
<!-- border: none !important;-->
<!-- }-->
.pq-grid .pq-grid-header {
background: #f8f9fa !important;
border-bottom: 2px solid #dee2e6 !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-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-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:nth-child(even) {-->
<!-- background-color: #f8f9fa !important;-->
<!-- }-->
.pq-grid .pq-grid-row:hover {
background-color: #e9ecef !important;
}
<!-- .pq-grid .pq-grid-row:hover {-->
<!-- background-color: #e9ecef !important;-->
<!-- }-->
</style>
</t>
</templates>