DPR
This commit is contained in:
parent
796a8e1e17
commit
5581b94b64
|
|
@ -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
|
|
@ -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>
|
||||
Loading…
Reference in New Issue