This commit is contained in:
Raman Marikanti 2026-02-12 14:53:38 +05:30
parent 796a8e1e17
commit 5581b94b64
3 changed files with 1239 additions and 730 deletions

View File

@ -43,6 +43,9 @@ class CustomerOrdersReport(models.AbstractModel):
from odoo import models, fields, api
from datetime import datetime, timedelta
import json
from datetime import date, datetime
import calendar
class CORDashboard(models.AbstractModel):
@ -257,107 +260,65 @@ class CORDashboard(models.AbstractModel):
except Exception as e:
return []
# Add this method to your cor.dashboard model
def get_dpr_daily_production_data(self):
"""Get DPR (Daily Production Report) data"""
@api.model
def get_dpr_daily_production_data(self, month, year):
try:
# Get current date
today = fields.Date.today()
first_day_of_month = today.replace(day=1)
last_day_of_prev_month = first_day_of_month - timedelta(days=1)
first_day_of_prev_month = last_day_of_prev_month.replace(day=1)
month = str(int(month))
year = str(int(year))
results = []
# Initialize result
result = []
# Get all products
products = self.env['product.product'].search([
('type', '=', 'product'),
('categ_id.complete_name', 'ilike', 'Finished Goods')
# Search for customer orders in selected month/year
co = self.env['customer.order'].search([
('order_month', '=', month),
('order_year', '=', year)
])
for product in products:
# Get pending orders from last month
pending_orders = self.env['sale.order.line'].search([
('product_id', '=', product.id),
('order_id.state', 'in', ['sale', 'done']),
('order_id.date_order', '>=', first_day_of_prev_month),
('order_id.date_order', '<=', last_day_of_prev_month),
('qty_delivered', '<', 'product_uom_qty')
for line in co.order_line_ids:
month_int = int(line.order_id.order_month)
year_int = int(line.order_id.order_year)
first_day = date(year_int, month_int, 1)
last_day = date(year_int, month_int,
calendar.monthrange(year_int, month_int)[1])
# Convert to datetime for date_start (Datetime field)
start_datetime = datetime.combine(first_day, datetime.min.time())
end_datetime = datetime.combine(last_day, datetime.max.time())
# Get all production orders for this customer order's products
production_orders = self.env['mrp.production'].search_read([
('product_id', '=', line.product_id.id),
('date_start', '>=', start_datetime),
('date_start', '<=', end_datetime),
], ['date_start', 'product_uom_qty'])
# Calculate pending orders from previous months
pending_orders = self.env['customer.order.line'].search([
('product_id', '=', line.product_id.id),
('order_id.customer_id', '=', line.order_id.customer_id.id),
('order_month', '!=', month),
('pending_dispatch', '>', 0)
])
pending_qty = sum(pending_orders.mapped('product_uom_qty')) - sum(pending_orders.mapped('qty_delivered'))
pending_value = pending_qty * product.standard_price
pending_order_qty = sum(pending_orders.mapped('pending_dispatch'))
# Get present month orders
present_orders = self.env['sale.order.line'].search([
('product_id', '=', product.id),
('order_id.state', 'in', ['sale', 'done']),
('order_id.date_order', '>=', first_day_of_month),
('order_id.date_order', '<=', today)
])
present_qty = sum(present_orders.mapped('product_uom_qty'))
present_value = present_qty * product.standard_price
# Get FG available quantity
fg_qty = product.qty_available
fg_value = fg_qty * product.standard_price
# Get dispatch qty for current month
moves = self.env['stock.move'].search([
('product_id', '=', product.id),
('state', '=', 'done'),
('date', '>=', first_day_of_month),
('date', '<=', today),
('location_dest_id.usage', '=', 'customer'),
('location_id.usage', '=', 'internal')
])
dispatch_qty = sum(moves.mapped('quantity_done'))
dispatch_value = dispatch_qty * product.standard_price
# Get daily production for last 31 days
daily_data = {}
for i in range(31, 0, -1):
day_date = today - timedelta(days=(31 - i))
day_moves = self.env['stock.move'].search([
('product_id', '=', product.id),
('state', '=', 'done'),
('date', '>=', day_date),
('date', '<', day_date + timedelta(days=1)),
('production_id', '!=', False) # Production moves
])
daily_data[f'day{i}_qty'] = sum(day_moves.mapped('quantity_done'))
# Calculate totals
total_order_qty = pending_qty + present_qty
total_order_value = pending_value + present_value
remaining_qty = max(0, total_order_qty - fg_qty)
remaining_value = max(0, total_order_value - fg_value)
result.append({
'product_id': product.id,
'product_name': product.display_name,
'pending_order_qty': pending_qty,
'pending_order_value': pending_value,
'present_month_order_qty': present_qty,
'present_month_order_value': present_value,
'total_order_qty': total_order_qty,
'total_order_value': total_order_value,
'fg_available_qty': fg_qty,
'fg_available_value': fg_value,
'dispatch_qty': dispatch_qty,
'dispatch_value': dispatch_value,
'remaining_to_produce_qty': remaining_qty,
'remaining_to_produce_value': remaining_value,
**daily_data
results.append({
'customer': line.order_id.customer_id.name,
'product': line.product_id.display_name,
'order_qty': line.order_qty,
'order_qty_kg': line.order_qty * line.product_id.weight,
'fg_qty': line.fg_available,
'pending_order': pending_order_qty,
'produced_qty': line.produced_qty,
'produced_qty_kg': line.produced_qty * line.product_id.weight,
'dispatch_qty': line.dispatched_qty,
'dispatched_qty_kg': line.dispatched_qty * line.product_id.weight,
'remaining_production': line.order_qty - (line.produced_qty + line.fg_available),
'date_wise': production_orders
})
return result
return results
except Exception as e:
# _logger.error(f"Error in get_dpr_daily_production_data: {str(e)}")
# logger.error(f"Error in get_dpr_daily_production_data: {str(e)}")
return []

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<templates xml:space="preserve">
<t t-name="CustomerOrderStatusGrid" owl="1">
<div class="customer-order-status-container overflow-auto">
<!-- Header -->
@ -8,6 +7,7 @@
<div class="header-card">
<div class="header-content">
<h5 class="header-title">Customer Order Status Dashboard</h5>
<!-- Simple Month/Year Filter - Auto filter on change -->
<div class="simple-filter" t-if="!state.isLoading">
<div class="row g-2 align-items-center">
@ -16,15 +16,18 @@
<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"/>
<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="header-stats" t-if="!state.isLoading">
<div class="stats-container">
<div class="stat-item">
<span class="stat-label">Total Orders:</span>
@ -40,8 +43,7 @@
</div>
<div class="stat-item">
<span class="stat-label">Production Rate:</span>
<span class="stat-value badge"
t-att-class="getCompletionRateClass(state.summary.completionRate)">
<span class="stat-value badge" t-att-class="getCompletionRateClass(state.summary.completionRate)">
<t t-esc="state.summary.completionRate.toFixed(1)"/>%
</span>
</div>
@ -59,60 +61,68 @@
<!-- Tabs Navigation -->
<ul class="nav nav-tabs dashboard-tabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active"
id="order-tab"
data-bs-toggle="tab"
data-bs-target="#order-content"
type="button"
role="tab"
aria-controls="order-content"
aria-selected="true"
t-on-click="onTabShow">
<button
class="nav-link active"
id="order-tab"
data-bs-toggle="tab"
data-bs-target="#order-content"
type="button"
role="tab"
aria-controls="order-content"
aria-selected="true"
t-on-click="onTabShow"
>
<i class="fa fa-users mr-2"></i>
Customer Order Status
<span class="badge bg-primary ms-2" t-esc="state.gridData.length"/>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link"
id="dispatch-tab"
data-bs-toggle="tab"
data-bs-target="#dispatch-content"
type="button"
role="tab"
aria-controls="dispatch-content"
aria-selected="false"
t-on-click="onTabShow">
<button
class="nav-link"
id="dispatch-tab"
data-bs-toggle="tab"
data-bs-target="#dispatch-content"
type="button"
role="tab"
aria-controls="dispatch-content"
aria-selected="false"
t-on-click="onTabShow"
>
<i class="fa fa-truck mr-2"></i>
Dispatch Summary
<span class="badge bg-success ms-2" t-esc="state.dispatchData.length"/>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link"
id="rm-tab"
data-bs-toggle="tab"
data-bs-target="#rm-content"
type="button"
role="tab"
aria-controls="rm-content"
aria-selected="false"
t-on-click="onTabShow">
<button
class="nav-link"
id="rm-tab"
data-bs-toggle="tab"
data-bs-target="#rm-content"
type="button"
role="tab"
aria-controls="rm-content"
aria-selected="false"
t-on-click="onTabShow"
>
<i class="fa fa-cubes mr-2"></i>
Raw Material Availability
<span class="badge bg-warning ms-2" t-esc="state.rmData.length"/>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link"
id="dpr-tab"
data-bs-toggle="tab"
data-bs-target="#dpr-content"
type="button"
role="tab"
aria-controls="dpr-content"
aria-selected="false"
t-on-click="onTabShow">
<button
class="nav-link"
id="dpr-tab"
data-bs-toggle="tab"
data-bs-target="#dpr-content"
type="button"
role="tab"
aria-controls="dpr-content"
aria-selected="false"
t-on-click="onTabShow"
>
<i class="fa fa-chart-line mr-2"></i>
DPR - Daily Production Report
<span class="badge bg-info ms-2" t-esc="state.dprData.length"/>
@ -123,10 +133,12 @@
<!-- Tab Content -->
<div class="tab-content dashboard-tab-content">
<!-- Tab 1: Customer Order Status -->
<div class="tab-pane fade show active"
id="order-content"
role="tabpanel"
aria-labelledby="order-tab">
<div
class="tab-pane fade show active"
id="order-content"
role="tabpanel"
aria-labelledby="order-tab"
>
<div class="tab-card">
<div class="tab-header d-flex justify-content-between align-items-center">
<div class="tab-stats">
@ -159,7 +171,7 @@
<button class="btn btn-sm btn-outline-secondary" t-on-click="refreshOrderStatus">
<i class="fa fa-refresh mr-1"></i> Refresh
</button>
<button class="btn btn-sm btn-outline-primary" t-on-click="() => exportGrid('order')">
<button class="btn btn-sm btn-outline-primary" t-on-click="exportOrderGrid">
<i class="fa fa-download mr-1"></i> Export
</button>
</div>
@ -181,10 +193,12 @@
</div>
<!-- Tab 2: Dispatch Summary -->
<div class="tab-pane fade"
id="dispatch-content"
role="tabpanel"
aria-labelledby="dispatch-tab">
<div
class="tab-pane fade"
id="dispatch-content"
role="tabpanel"
aria-labelledby="dispatch-tab"
>
<div class="tab-card">
<div class="tab-header d-flex justify-content-between align-items-center">
<div class="tab-stats">
@ -217,7 +231,7 @@
<button class="btn btn-sm btn-outline-secondary" t-on-click="refreshDispatchSummary">
<i class="fa fa-refresh mr-1"></i> Refresh
</button>
<button class="btn btn-sm btn-outline-primary" t-on-click="() => exportGrid('dispatch')">
<button class="btn btn-sm btn-outline-primary" t-on-click="exportDispatchGrid">
<i class="fa fa-download mr-1"></i> Export
</button>
</div>
@ -239,10 +253,12 @@
</div>
<!-- Tab 3: Raw Material Availability -->
<div class="tab-pane fade"
id="rm-content"
role="tabpanel"
aria-labelledby="rm-tab">
<div
class="tab-pane fade"
id="rm-content"
role="tabpanel"
aria-labelledby="rm-tab"
>
<div class="tab-card">
<div class="tab-header d-flex justify-content-between align-items-center">
<div class="tab-stats">
@ -275,7 +291,7 @@
<button class="btn btn-sm btn-outline-secondary" t-on-click="refreshRMAvailability">
<i class="fa fa-refresh mr-1"></i> Refresh
</button>
<button class="btn btn-sm btn-outline-primary" t-on-click="() => exportGrid('rm')">
<button class="btn btn-sm btn-outline-primary" t-on-click="exportRMGrid">
<i class="fa fa-download mr-1"></i> Export
</button>
</div>
@ -297,10 +313,12 @@
</div>
<!-- Tab 4: DPR - Daily Production Report -->
<div class="tab-pane fade"
id="dpr-content"
role="tabpanel"
aria-labelledby="dpr-tab">
<div
class="tab-pane fade"
id="dpr-content"
role="tabpanel"
aria-labelledby="dpr-tab"
>
<div class="tab-card">
<div class="tab-header d-flex justify-content-between align-items-center">
<div class="tab-stats">
@ -333,7 +351,7 @@
<button class="btn btn-sm btn-outline-secondary" t-on-click="refreshDPRData">
<i class="fa fa-refresh mr-1"></i> Refresh
</button>
<button class="btn btn-sm btn-outline-primary" t-on-click="() => exportGrid('dpr')">
<button class="btn btn-sm btn-outline-primary" t-on-click="exportDPRGrid">
<i class="fa fa-download mr-1"></i> Export
</button>
</div>
@ -343,17 +361,6 @@
<i class="fa fa-info-circle fa-2x text-muted mb-3"></i>
<p class="text-muted">No DPR data available</p>
</div>
<div class="dpr-formula-section mb-3 p-3 bg-light rounded">
<h6 class="mb-2"><i class="fa fa-calculator mr-2"></i>Formulas:</h6>
<div class="formula-items">
<div class="formula-item">
<strong>Total Order Qty</strong> = Pending Order + Present Month Order
</div>
<div class="formula-item">
<strong>Remaining to Produce</strong> = Total Order Qty Closing FG
</div>
</div>
</div>
<div t-if="state.dprData.length > 0" class="grid-container-full">
<div t-ref="dprGrid" class="grid-element-full"></div>
</div>
@ -438,7 +445,7 @@
padding: 5px 10px;
min-width: 60px;
text-align: center;
color : white;
color: white;
}
.refresh-btn {
@ -762,76 +769,48 @@
}
/* Simple Filter Styles - Right side */
.simple-filter {
margin-top: 15px;
padding: 10px 0;
}
.simple-filter {
margin-top: 15px;
padding: 10px 0;
}
/* Position filter on right side */
.col-auto.ms-auto {
margin-left: auto !important;
}
/* Position filter on right side */
.col-auto.ms-auto {
margin-left: auto !important;
}
.input-group-sm {
width: 160px;
}
.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 .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;
}
.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);
}
/* 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;
}
/* 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;-->
<!-- }-->
.simple-filter {
text-align: center;
}
}
</style>
</t>
</templates>