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: else:
return [] 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 @api.model
def get_sale_margin_data(self, from_date, to_date): def get_sale_margin_data(self, from_date, to_date):
# Get user's timezone # Get user's timezone

View File

@ -4,6 +4,18 @@ import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { standardActionServiceProps } from "@web/webclient/actions/action_service"; 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 { export class ConsumptionDashboard extends Component {
static props = { static props = {
...standardActionServiceProps, ...standardActionServiceProps,
@ -102,7 +114,7 @@ export class ConsumptionDashboard extends Component {
} }
// Check for 'Outer bag' in product name (case insensitive) // Check for 'Outer bag' in product name (case insensitive)
if (row.product_name && row.product_name.toLowerCase().includes('outer bag')) { 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 // Default to first 2 characters of product code
if (row.product_code && row.product_code.length >= 2) { if (row.product_code && row.product_code.length >= 2) {
@ -170,12 +182,12 @@ export class ConsumptionDashboard extends Component {
width: 120, width: 120,
filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] } filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] }
}, },
{ title: "Date", dataIndx: "date", width: 150 }, { title: "Date", dataIndx: "date", width: 100 },
{ {
title: "Product Name", title: "Product Name",
dataIndx: "product_name", dataIndx: "product_name",
width: 250, width: 300,
filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] } filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] }
}, },
{ {
@ -205,7 +217,7 @@ export class ConsumptionDashboard extends Component {
width: 80 width: 80
}, },
{ {
title: "Value", title: "Value (INR)",
dataIndx: "value", dataIndx: "value",
width: 120, width: 120,
dataType: "float", dataType: "float",
@ -218,7 +230,7 @@ export class ConsumptionDashboard extends Component {
const getColumnPriority = (prefix) => { const getColumnPriority = (prefix) => {
const priorityMap = { const priorityMap = {
'Laminate': 1, 'Laminate': 1,
'Outer bag': 2, 'Outer Bags': 2,
'RM':3, 'RM':3,
'PM':4, 'PM':4,
'ST':5, 'ST':5,
@ -250,19 +262,22 @@ export class ConsumptionDashboard extends Component {
// Create dynamic columns // Create dynamic columns
const dynamicColumns = sortedPrefixes.map(prefix => ({ const dynamicColumns = sortedPrefixes.map(prefix => ({
dataIndx: 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, width: 120,
align: "right", align: "right",
dataType: "float", dataType: "float",
format: "#,###.00", format: "#,###.00",
summary: { type: "sum" }, 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]; return [...baseColumns, ...dynamicColumns];

View File

@ -4,6 +4,15 @@ import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { standardActionServiceProps } from "@web/webclient/actions/action_service"; 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 { 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() { initializeDates() {
const today = new Date(); const today = new Date();
const firstDay = new Date(today.getFullYear(), today.getMonth(), 1); 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: "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: "Customer", dataIndx: "customer", width: 280, filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] } },
{ title: "Tags", dataIndx: "tags", width: 100, }, { 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: "Weight (kgs)", dataIndx: "weight", width: 120 },
{ title: "Production Cost (INR)", dataIndx: "cost", width: 150, 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: "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 (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: "Margin %", dataIndx: "margin_percent", width: 120, dataType: "float", format: "#,###.00", summary: { type: "sum_" }, align: "right" },
{ {
title: "View", title: "View",
width: 120, width: 120,
@ -135,6 +165,10 @@ export class SaleDashboard extends Component {
async renderSaleGrid() { async renderSaleGrid() {
const columns = await this.getSaleColumns() const columns = await this.getSaleColumns()
const agg = pq.aggregate; const agg = pq.aggregate;
agg.sum_ = function(arr, col) {
return " " + agg.sum(arr, col).toFixed(2).toString();
};
const gridOptions = { const gridOptions = {
selectionModel: { type: "row" }, selectionModel: { type: "row" },

View File

@ -56,7 +56,67 @@
</div> </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 shadow-sm">
<div class="card-header bg-success text-white"> <div class="card-header bg-success text-white">
<h5 class="mb-0"> <h5 class="mb-0">

View File

@ -4,6 +4,17 @@ import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { standardActionServiceProps } from "@web/webclient/actions/action_service"; 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 { export class StockDashboard extends Component {
static props = { static props = {
...standardActionServiceProps, ...standardActionServiceProps,
@ -17,6 +28,7 @@ export class StockDashboard extends Component {
this.state = useState({ this.state = useState({
rows: [], rows: [],
category:[],
fromDate: "", fromDate: "",
toDate: "" toDate: ""
}); });
@ -92,7 +104,71 @@ export class StockDashboard extends Component {
console.error("Error loading data:", error); 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() { async getColumns() {
return [ return [
{ title: "Product Code", dataIndx: "product_code", width: 100, filter: { type: 'textbox', condition: 'contain', listeners: ['keyup'] } }, { 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%", width: "100%",
height: "100%", height: "100%",
editable: false, editable: false,
freezeCols: 2,
stripeRows: true, 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" }, dataModel: { data: this.state.rows, location: "local", sorting: "local", paging: "local" },
colModel: columns, colModel: columns,
toolbar: { toolbar: {

View File

@ -57,6 +57,66 @@
</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 shadow-sm">
<div class="card-header bg-primary text-white"> <div class="card-header bg-primary text-white">
<h5 class="mb-0"> <h5 class="mb-0">

View File

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