new dashboards views
This commit is contained in:
parent
aa1fd0c881
commit
ccb7abe76a
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
|
|
@ -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" },
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue