new dashboards views

This commit is contained in:
raman 2025-11-18 10:35:18 +05:30
parent aa1fd0c881
commit ccb7abe76a
7 changed files with 338 additions and 77 deletions

View File

@ -140,6 +140,12 @@ class SamashtiDashboard(models.AbstractModel):
else:
return []
@api.model
def get_products_data(self):
all_prod = self.env['product.product'].search([('type', '=', 'consu')])
all_category = all_prod.category_id
@api.model
def get_sale_margin_data(self, from_date, to_date):
# Get user's timezone

View File

@ -4,6 +4,18 @@ import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
import { standardActionServiceProps } from "@web/webclient/actions/action_service";
function formatFloat(value) {
if (value === null || value === undefined || isNaN(value)) {
return "0.00";
}
return parseFloat(value).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
export class ConsumptionDashboard extends Component {
static props = {
...standardActionServiceProps,
@ -102,7 +114,7 @@ export class ConsumptionDashboard extends Component {
}
// Check for 'Outer bag' in product name (case insensitive)
if (row.product_name && row.product_name.toLowerCase().includes('outer bag')) {
return 'Outer bag';
return 'Outer Bags';
}
// Default to first 2 characters of product code
if (row.product_code && row.product_code.length >= 2) {
@ -170,12 +182,12 @@ export class ConsumptionDashboard extends Component {
width: 120,
filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] }
},
{ title: "Date", dataIndx: "date", width: 150 },
{ title: "Date", dataIndx: "date", width: 100 },
{
title: "Product Name",
dataIndx: "product_name",
width: 250,
width: 300,
filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] }
},
{
@ -205,7 +217,7 @@ export class ConsumptionDashboard extends Component {
width: 80
},
{
title: "Value",
title: "Value (INR)",
dataIndx: "value",
width: 120,
dataType: "float",
@ -218,7 +230,7 @@ export class ConsumptionDashboard extends Component {
const getColumnPriority = (prefix) => {
const priorityMap = {
'Laminate': 1,
'Outer bag': 2,
'Outer Bags': 2,
'RM':3,
'PM':4,
'ST':5,
@ -250,19 +262,22 @@ export class ConsumptionDashboard extends Component {
// Create dynamic columns
const dynamicColumns = sortedPrefixes.map(prefix => ({
dataIndx: prefix,
title: prefix+" ₹" ,
title: (() => {
let label;
if (prefix === 'FU') label = 'Fuel';
else if (prefix === 'MA') label = 'Maintenance';
else if (prefix === 'MP') label = 'Manpower';
else if (prefix === 'PW') label = 'Power';
else label = prefix;
return label + " (INR)";
})(),
width: 120,
align: "right",
dataType: "float",
format: "#,###.00",
summary: { type: "sum" },
render: function(ui) {
const value = ui.cellData;
// if (value > 0) {
// return `<span >${value}</span>`;
// }
return value ? value.toFixed(2) : "0";
}
}));
return [...baseColumns, ...dynamicColumns];

View File

@ -4,6 +4,15 @@ import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
import { standardActionServiceProps } from "@web/webclient/actions/action_service";
function formatFloat(value) {
if (value === null || value === undefined || isNaN(value)) {
return "0.00";
}
return parseFloat(value).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
export class SaleDashboard extends Component {
@ -49,6 +58,27 @@ export class SaleDashboard extends Component {
}
}
getTotalProductionValue() {
return formatFloat(this.state.sale_rows.reduce((sum, row) => sum + (row.cost || 0), 0).toFixed(2)) + " ₹";
}
getTotalSaleValue() {
return formatFloat(this.state.sale_rows.reduce((sum, row) => sum + (row.sale_price || 0), 0).toFixed(2)) + " ₹";
}
getTotalMarginValue() {
return formatFloat(this.state.sale_rows.reduce((sum, row) => sum + (row.margin || 0), 0).toFixed(2)) + " ₹";
}
getMarginPercentage() {
const totalMarginValue = this.state.sale_rows.reduce((sum, row) => sum + (row.margin || 0), 0).toFixed(2)
const totalSaleValue = this.state.sale_rows.reduce((sum, row) => sum + (row.sale_price || 0), 0).toFixed(2)
if (totalSaleValue === 0) return 0;
const percentage = (totalMarginValue / totalSaleValue) * 100;
return formatFloat(percentage.toFixed(2)); // Returns with 2 decimal places
}
initializeDates() {
const today = new Date();
const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
@ -103,12 +133,12 @@ export class SaleDashboard extends Component {
{ title: "Invoice", dataIndx: "invoice", width: 180, filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] } },
{ title: "Customer", dataIndx: "customer", width: 280, filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] } },
{ title: "Tags", dataIndx: "tags", width: 100, },
{ title: "Quantity (Bags/No's)", dataIndx: "quantity", width: 150, dataType: "float", format: "#,###.00", summary: { type: "sum" }, align: "right" },
{ title: "Quantity (Bags/No's)", dataIndx: "quantity", width: 150, dataType: "float", format: "#,###.00", summary: { type: "sum_" }, align: "right" },
{ title: "Weight (kgs)", dataIndx: "weight", width: 120 },
{ title: "Production Cost (INR)", dataIndx: "cost", width: 150, dataType: "float", format: "#,###.00", summary: { type: "sum" }, align: "right" },
{ title: "Sale Price (INR)", dataIndx: "sale_price", width: 120, dataType: "float", format: "#,###.00", summary: { type: "sum" }, align: "right" },
{ title: "Margin (INR)", dataIndx: "margin", width: 120, dataType: "float", format: "#,###.00", summary: { type: "sum" }, align: "right" },
{ title: "Margin %", dataIndx: "margin_percent", width: 120, dataType: "float", format: "#,###.00", summary: { type: "sum" }, align: "right" },
{ title: "Production Cost (INR)", dataIndx: "cost", width: 150, dataType: "float", format: "#,###.00", summary: { type: "sum_" }, align: "right" },
{ title: "Sale Price (INR)", dataIndx: "sale_price", width: 120, dataType: "float", format: "#,###.00", summary: { type: "sum_" }, align: "right" },
{ title: "Margin (INR)", dataIndx: "margin", width: 120, dataType: "float", format: "#,###.00", summary: { type: "sum_" }, align: "right" },
{ title: "Margin %", dataIndx: "margin_percent", width: 120, dataType: "float", format: "#,###.00", summary: { type: "sum_" }, align: "right" },
{
title: "View",
width: 120,
@ -135,6 +165,10 @@ export class SaleDashboard extends Component {
async renderSaleGrid() {
const columns = await this.getSaleColumns()
const agg = pq.aggregate;
agg.sum_ = function(arr, col) {
return " " + agg.sum(arr, col).toFixed(2).toString();
};
const gridOptions = {
selectionModel: { type: "row" },

View File

@ -55,8 +55,68 @@
</div>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col mb-4">
<div class="card shadow-sm h-100">
<div class="card-body text-center py-4">
<div class="text-primary mb-3">
<i class="fas fa-receipt fa-2x"></i>
</div>
<h6 class="card-title text-muted small mb-2">Total Invoices</h6>
<h4 class="text-primary fw-bold mb-0" t-esc="state.sale_rows.length"/>
</div>
</div>
</div>
<div class="col mb-4">
<div class="card shadow-sm h-100">
<div class="card-body text-center py-4">
<div class="text-danger mb-3">
<i class="fas fa-industry fa-2x"></i>
</div>
<h6 class="card-title text-muted small mb-2">Total Production</h6>
<h4 class="text-danger fw-bold mb-0" t-esc="this.getTotalProductionValue()"/>
</div>
</div>
</div>
<div class="col mb-4">
<div class="card shadow-sm h-100">
<div class="card-body text-center py-4">
<div class="text-warning mb-3">
<i class="fas fa-shopping-cart fa-2x"></i>
</div>
<h6 class="card-title text-muted small mb-2">Total Sale</h6>
<h4 class="text-warning fw-bold mb-0" t-esc="this.getTotalSaleValue()"/>
</div>
</div>
</div>
<div class="col mb-4">
<div class="card shadow-sm h-100">
<div class="card-body text-center py-4">
<div class="text-success mb-3">
<i class="fas fa-chart-line fa-2x"></i>
</div>
<h6 class="card-title text-muted small mb-2">Total Margin</h6>
<h4 class="text-success fw-bold mb-0" t-esc="this.getTotalMarginValue()"/>
</div>
</div>
</div>
<div class="col mb-4">
<div class="card shadow-sm h-100">
<div class="card-body text-center py-4">
<div class="text-info mb-3">
<i class="fas fa-percentage fa-2x"></i>
</div>
<h6 class="card-title text-muted small mb-2">Margin %</h6>
<h4 class="text-info fw-bold mb-0" t-esc="this.getMarginPercentage() + '%'"/>
</div>
</div>
</div>
</div>
<div class="card shadow-sm">
<div class="card-header bg-success text-white">
<h5 class="mb-0">

View File

@ -4,6 +4,17 @@ import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
import { standardActionServiceProps } from "@web/webclient/actions/action_service";
function formatFloat(value) {
if (value === null || value === undefined || isNaN(value)) {
return "0.00";
}
return parseFloat(value).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
export class StockDashboard extends Component {
static props = {
...standardActionServiceProps,
@ -17,6 +28,7 @@ export class StockDashboard extends Component {
this.state = useState({
rows: [],
category:[],
fromDate: "",
toDate: ""
});
@ -92,7 +104,71 @@ export class StockDashboard extends Component {
console.error("Error loading data:", error);
}
}
getTotalStockValue() {
return formatFloat(this.state.rows.reduce((sum, row) => sum + (row.value || 0), 0).toFixed(2)) + " ₹";
}
parseFloatValue(value) {
if (value === null || value === undefined || value === "") {
return 0;
}
if (typeof value === 'number') {
return value;
}
if (typeof value === 'string') {
let cleaned = value.replace(/[^\d.-]/g, '');
let result = parseFloat(cleaned);
return isNaN(result) ? 0 : result;
}
return 0;
}
// Stock dashboard calculation methods
getTotalProductsCount() {
// Count unique products based on product_code
const uniqueProducts = new Set(
this.state.rows
.filter(row => row.product_code)
.map(row => row.product_code)
);
return uniqueProducts.size;
}
getCategoryCount() {
// Count unique categories
const uniqueCategories = new Set(
this.state.rows
.filter(row => row.category)
.map(row => row.category)
);
return uniqueCategories.size;
}
getOutOfStockCount() {
return this.state.rows.filter(row => {
const currentStock = this.parseFloatValue(row.closing_stock || row.quantity || 0);
return currentStock <= 0;
}).length;
}
getLowStockCount() {
return this.state.rows.filter(row => {
const currentStock = this.parseFloatValue(row.closing_stock || row.quantity || 0);
const minStock = this.parseFloatValue(row.min_stock) || 50; // Default minimum stock
return currentStock > 0 && currentStock < minStock;
}).length;
}
getLowStockPercentage() {
const totalProducts = this.getTotalProductsCount();
const lowStockCount = this.getLowStockCount();
if (totalProducts === 0) return "0.00";
const percentage = (lowStockCount / totalProducts) * 100;
return formatFloat(percentage);
}
async getColumns() {
return [
{ title: "Product Code", dataIndx: "product_code", width: 100, filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] } },
@ -118,8 +194,13 @@ export class StockDashboard extends Component {
width: "100%",
height: "100%",
editable: false,
freezeCols: 2,
stripeRows: true,
filterModel: { on: true, mode: "AND", header: true, autoSearch: true, type: 'local', minLength: 1 },
menuIcon: true, //show header menu icon initially.
menuUI: {
tabs: ['filter'] //display only filter tab.
},
filterModel: { on: true, mode: "AND", header: true, autoSearch: true, type: 'local', minLength: 1,menuIcon: true },
dataModel: { data: this.state.rows, location: "local", sorting: "local", paging: "local" },
colModel: columns,
toolbar: {

View File

@ -55,7 +55,67 @@
</div>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<!-- Total Products Card -->
<div class="col mb-4">
<div class="card shadow-sm h-100">
<div class="card-body text-center py-4">
<div class="text-primary mb-3">
<i class="fas fa-cubes fa-2x"></i>
</div>
<h6 class="card-title text-muted small mb-2">Total Products</h6>
<h4 class="text-primary fw-bold mb-0">
<span t-esc="this.getTotalProductsCount()"/>
</h4>
<small class="text-muted" t-esc="'Across ' + this.getCategoryCount() + ' categories'"/>
</div>
</div>
</div>
<!-- Current Stock Value Card -->
<div class="col mb-4">
<div class="card shadow-sm h-100">
<div class="card-body text-center py-4">
<div class="text-success mb-3">
<i class="fas fa-dollar-sign fa-2x"></i>
</div>
<h6 class="card-title text-muted small mb-2">Current Stock Value</h6>
<h4 class="text-success fw-bold mb-0" t-esc="this.getTotalStockValue()"/>
<small class="text-muted">Total inventory value</small>
</div>
</div>
</div>
<!-- Out of Stock Products Card -->
<div class="col mb-4">
<div class="card shadow-sm h-100">
<div class="card-body text-center py-4">
<div class="text-danger mb-3">
<i class="fas fa-times-circle fa-2x"></i>
</div>
<h6 class="card-title text-muted small mb-2">Out of Stock</h6>
<h4 class="text-danger fw-bold mb-0" t-esc="this.getOutOfStockCount()"/>
<small class="text-muted">Products need restocking</small>
</div>
</div>
</div>
<!-- Low Stock Percentage Card -->
<div class="col mb-4">
<div class="card shadow-sm h-100">
<div class="card-body text-center py-4">
<div class="text-info mb-3">
<i class="fas fa-exclamation-triangle fa-2x"></i>
</div>
<h6 class="card-title text-muted small mb-2">Low Stock Alert</h6>
<h4 class="text-info fw-bold mb-0" t-esc="this.getLowStockCount()"/>
<small class="text-muted">Below minimum levels</small>
</div>
</div>
</div>
</div>
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<menuitem id="samashti_dashboard_root"
name="Overview"
action="stock_dashboard_action"
sequence="-100" groups="dashboard.group_proforma_sales_dashboard"/>
<!-- Stock Dashboard -->
<record id="stock_dashboard_action" model="ir.actions.client">
<field name="name">Stock Dashboard</field>
@ -11,6 +8,22 @@
<field name="context">{'user_id':uid}</field>
</record>
<record id="consumption_dashboard_action" model="ir.actions.client">
<field name="name">Consumption Dashboard</field>
<field name="tag">ConsumptionDashboard</field>
</record>
<record id="sale_dashboard_action" model="ir.actions.client">
<field name="name">Sale Margin Dashboard</field>
<field name="tag">SaleDashboard</field>
<field name="context">{'user_id':uid}</field>
</record>
<menuitem id="samashti_dashboard_root"
name="Overview"
action="stock_dashboard_action"
sequence="-100" groups="dashboard.group_proforma_sales_dashboard"/>
<menuitem id="stock_dashboard_menu"
name="Stock Dashboard"
action="stock_dashboard_action"
@ -18,11 +31,6 @@
parent="samashti_dashboard_root"
groups="dashboard.group_proforma_sales_dashboard"/>
<record id="sale_dashboard_action" model="ir.actions.client">
<field name="name">Sale Margin Dashboard</field>
<field name="tag">SaleDashboard</field>
<field name="context">{'user_id':uid}</field>
</record>
<menuitem id="sale_dashboard_menu"
name="Sale Margin Report"
@ -31,10 +39,7 @@
parent="samashti_dashboard_root"
groups="dashboard.group_proforma_sales_dashboard"/>
<record id="consumption_dashboard_action" model="ir.actions.client">
<field name="name">Consumption Dashboard</field>
<field name="tag">ConsumptionDashboard</field>
</record>
<menuitem id="consumption_dashboard_menu"
name="Consumption Report"