PSI进销存系统数据可视化与经营分析模块
经营分析概述
数据可视化与经营分析是企业决策的重要支撑。PSI系统提供完善的经营看板、数据报表和分析工具,帮助企业管理者快速了解经营状况,发现问题并做出正确决策。
核心经营看板
经营看板展示企业关键经营指标:
实时经营大屏
// dashboard.js - 经营看板数据服务
class DashboardService {
// 获取今日经营数据
async getTodaySummary() {
const today = moment().format('YYYY-MM-DD');
const [sales, purchases, inventory] = await Promise.all([
// 今日销售
SalesOrder.aggregate([
{ $match: { orderDate: today, status: { $ne: 'cancelled' } } },
{
$group: {
_id: null,
orderCount: { $sum: 1 },
totalAmount: { $sum: '$totalAmount' },
profit: { $sum: '$profit' }
}
}
]),
// 今日采购
PurchaseOrder.aggregate([
{ $match: { orderDate: today, status: 'received' } },
{
$group: {
_id: null,
orderCount: { $sum: 1 },
totalAmount: { $sum: '$totalAmount' }
}
}
]),
// 库存概览
Inventory.aggregate([
{
$group: {
_id: null,
totalValue: { $sum: { $multiply: ['$quantity', '$costPrice'] } },
totalQuantity: { $sum: '$quantity' },
lowStockCount: {
$sum: { $cond: [{ $lt: ['$quantity', '$minStock'] }, 1, 0] }
}
}
}
])
]);
return {
todaySales: {
orderCount: sales[0]?.orderCount || 0,
amount: sales[0]?.totalAmount || 0,
profit: sales[0]?.profit || 0,
growth: await this.calculateGrowth('sales', today)
},
todayPurchases: {
orderCount: purchases[0]?.orderCount || 0,
amount: purchases[0]?.totalAmount || 0
},
inventory: {
totalValue: inventory[0]?.totalValue || 0,
totalQuantity: inventory[0]?.totalQuantity || 0,
lowStockCount: inventory[0]?.lowStockCount || 0
}
};
}
// 计算增长率
async calculateGrowth(type, today) {
const todayData = await this.getDataByDate(type, today);
const yesterdayData = await this.getDataByDate(
type,
moment(today).subtract(1, 'day').format('YYYY-MM-DD')
);
if (yesterdayData === 0) return 0;
return ((todayData - yesterdayData) / yesterdayData * 100).toFixed(1);
}
}
销售趋势图表
// chart-renderer.js - 图表渲染器
const ChartRenderer = {
// 销售趋势折线图
renderSalesTrend(containerId, data) {
const chart = echarts.init(document.getElementById(containerId));
const option = {
tooltip: {
trigger: 'axis',
formatter: '{b}: {c}元'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: data.dates,
axisLine: {
lineStyle: { color: '#ccc' }
}
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: { color: '#ccc' }
},
splitLine: {
lineStyle: { type: 'dashed' }
}
},
series: [
{
name: '销售额',
type: 'line',
smooth: true,
data: data.sales,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(84, 112, 198, 0.5)' },
{ offset: 1, color: 'rgba(84, 112, 198, 0.1)' }
])
},
itemStyle: {
color: '#5470c6'
}
},
{
name: '利润',
type: 'line',
smooth: true,
data: data.profit,
itemStyle: {
color: '#91cc75'
}
}
]
};
chart.setOption(option);
return chart;
},
// 销售分类饼图
renderCategoryPie(containerId, data) {
const chart = echarts.init(document.getElementById(containerId));
const option = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '销售分类',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 20,
fontWeight: 'bold'
}
},
data: data.map(item => ({
value: item.amount,
name: item.categoryName
}))
}
]
};
chart.setOption(option);
return chart;
},
// 库存周转雷达图
renderInventoryRadar(containerId, data) {
const chart = echarts.init(document.getElementById(containerId));
const option = {
tooltip: {},
radar: {
indicator: data.categories.map(c => ({ name: c, max: 100 })),
shape: 'circle',
splitNumber: 5,
axisName: {
color: '#666'
}
},
series: [
{
name: '库存健康度',
type: 'radar',
data: [
{
value: data.values,
name: '当前指标',
areaStyle: {
color: 'rgba(84, 112, 198, 0.3)'
}
}
]
}
]
};
chart.setOption(option);
return chart;
}
};
多维度分析报表
销售明细报表
// reports/sales-report.js
class SalesReportService {
// 销售明细报表
async getSalesDetailReport(params) {
const { startDate, endDate, customerId, productId, warehouseId } = params;
const matchConditions = {
orderDate: {
$gte: startDate,
$lte: endDate
},
status: { $ne: 'cancelled' }
};
if (customerId) matchConditions.customerId = customerId;
if (warehouseId) matchConditions.warehouseId = warehouseId;
const pipeline = [
{ $match: matchConditions },
{ $unwind: '$items' },
// 关联商品信息
{
$lookup: {
from: 'products',
localField: 'items.productId',
foreignField: 'id',
as: 'productInfo'
}
},
{ $unwind: '$productInfo' },
// 关联客户信息
{
$lookup: {
from: 'customers',
localField: 'customerId',
foreignField: 'id',
as: 'customerInfo'
}
},
{ $unwind: '$customerInfo' },
// 投影所需字段
{
$project: {
orderNo: 1,
orderDate: 1,
customerName: '$customerInfo.name',
productCode: '$productInfo.code',
productName: '$productInfo.name',
quantity: '$items.quantity',
unitPrice: '$items.unitPrice',
amount: { $multiply: ['$items.quantity', '$items.unitPrice'] },
discount: '$items.discount',
finalAmount: { $multiply: ['$items.quantity', '$items.finalPrice'] },
profit: {
$multiply: [
'$items.quantity',
{ $subtract: ['$items.finalPrice', '$items.costPrice'] }
]
},
warehouseName: 1,
salesman: 1
}
},
{ $sort: { orderDate: -1 } }
];
// 如果有商品筛选
if (productId) {
pipeline.splice(1, 0, {
$match: { 'items.productId': productId }
});
}
return await SalesOrder.aggregate(pipeline);
}
// 销售汇总报表(按客户)
async getSalesSummaryByCustomer(params) {
const pipeline = [
{ $match: this.buildMatchConditions(params) },
{ $unwind: '$items' },
{
$group: {
_id: '$customerId',
customerName: { $first: '$customerName' },
orderCount: { $sum: 1 },
totalAmount: { $sum: '$totalAmount' },
totalProfit: { $sum: '$profit' },
avgOrderAmount: { $avg: '$totalAmount' }
}
},
{
$addFields: {
profitRate: {
$multiply: [
{ $divide: ['$totalProfit', '$totalAmount'] },
100
]
}
}
},
{ $sort: { totalAmount: -1 } }
];
return await SalesOrder.aggregate(pipeline);
}
// 销售汇总报表(按商品)
async getSalesSummaryByProduct(params) {
const pipeline = [
{ $match: this.buildMatchConditions(params) },
{ $unwind: '$items' },
{
$group: {
_id: '$items.productId',
productCode: { $first: '$items.productCode' },
productName: { $first: '$items.productName' },
quantity: { $sum: '$items.quantity' },
totalAmount: {
$sum: { $multiply: ['$items.quantity', '$items.finalPrice'] }
},
totalCost: {
$sum: { $multiply: ['$items.quantity', '$items.costPrice'] }
}
}
},
{
$addFields: {
profit: { $subtract: ['$totalAmount', '$totalCost'] },
profitRate: {
$multiply: [
{
$divide: [
{ $subtract: ['$totalAmount', '$totalCost'] },
'$totalAmount'
]
},
100
]
}
}
},
{ $sort: { totalAmount: -1 } }
];
return await SalesOrder.aggregate(pipeline);
}
buildMatchConditions(params) {
const { startDate, endDate, warehouseId } = params;
const conditions = {
orderDate: { $gte: startDate, $lte: endDate },
status: { $ne: 'cancelled' }
};
if (warehouseId) conditions.warehouseId = warehouseId;
return conditions;
}
}
库存周转分析
// reports/inventory-turnover.js
class InventoryTurnoverService {
// 计算库存周转率
async calculateTurnoverRate(warehouseId, period = 30) {
const startDate = moment().subtract(period, 'days').format('YYYY-MM-DD');
const endDate = moment().format('YYYY-MM-DD');
// 获取期间销售成本
const salesCost = await this.getSalesCost(startDate, endDate, warehouseId);
// 获取平均库存
const avgInventory = await this.getAverageInventory(warehouseId, period);
// 周转率 = 销售成本 / 平均库存
const turnoverRate = avgInventory > 0
? (salesCost / avgInventory).toFixed(2)
: 0;
return {
period,
salesCost,
avgInventory,
turnoverRate: parseFloat(turnoverRate),
turnoverDays: Math.round(period / turnoverRate)
};
}
// 获取销售成本
async getSalesCost(startDate, endDate, warehouseId) {
const result = await SalesOrder.aggregate([
{
$match: {
orderDate: { $gte: startDate, $lte: endDate },
status: { $ne: 'cancelled' },
...(warehouseId && { warehouseId })
}
},
{ $unwind: '$items' },
{
$group: {
_id: null,
totalCost: {
$sum: { $multiply: ['$items.quantity', '$items.costPrice'] }
}
}
}
]);
return result[0]?.totalCost || 0;
}
// 获取平均库存
async getAverageInventory(warehouseId, period) {
const dates = this.getDateRange(period);
const inventoryRecords = [];
for (const date of dates) {
const record = await InventorySnapshot.findOne({
date,
...(warehouseId && { warehouseId })
});
inventoryRecords.push(record?.totalValue || 0);
}
return inventoryRecords.reduce((a, b) => a + b, 0) / inventoryRecords.length;
}
getDateRange(period) {
const dates = [];
for (let i = period; i >= 0; i--) {
dates.push(moment().subtract(i, 'days').format('YYYY-MM-DD'));
}
return dates;
}
}
智能预警提醒
// alerts/intelligence-alert.js
class IntelligenceAlert {
constructor() {
this.rules = this.loadAlertRules();
}
// 检查预警规则
async checkAlerts() {
const alerts = [];
// 1. 库存预警
const lowStockProducts = await this.checkLowStock();
if (lowStockProducts.length > 0) {
alerts.push({
type: 'low_stock',
level: 'warning',
title: '库存不足预警',
content: `${lowStockProducts.length}个商品库存低于安全库存`,
items: lowStockProducts.slice(0, 5)
});
}
// 2. 应收账款预警
const overdueReceivables = await this.checkOverdueReceivables();
if (overdueReceivables.length > 0) {
alerts.push({
type: 'overdue_receivable',
level: 'danger',
title: '应收账款逾期',
content: `${overdueReceivables.length}笔应收账款已逾期`,
items: overdueReceivables.slice(0, 5)
});
}
// 3. 销售异常预警
const salesAnomaly = await this.checkSalesAnomaly();
if (salesAnomaly) {
alerts.push({
type: 'sales_anomaly',
level: 'info',
title: '销售数据异常',
content: `今日销售额同比下降${Math.abs(salesAnomaly)}%`
});
}
// 4. 供应商预警
const supplierIssues = await this.checkSupplierIssues();
if (supplierIssues.length > 0) {
alerts.push({
type: 'supplier_issue',
level: 'warning',
title: '供应商问题',
content: `${supplierIssues.length}个供应商存在待处理问题`,
items: supplierIssues.slice(0, 5)
});
}
return alerts;
}
// 检查低库存
async checkLowStock() {
return await Inventory.find({
$expr: { $lte: ['$quantity', '$minStock'] }
}).limit(10);
}
// 检查应收账款逾期
async checkOverdueReceivables() {
const today = moment().format('YYYY-MM-DD');
return await Receivable.find({
dueDate: { $lt: today },
status: { $ne: 'paid' },
amount: { $gt: 0 }
}).sort({ dueDate: 1 });
}
// 检查销售异常
async checkSalesAnomaly() {
const today = moment().format('YYYY-MM-DD');
const yesterday = moment().subtract(1, 'day').format('YYYY-MM-DD');
const todaySales = await this.getDailySales(today);
const yesterdaySales = await this.getDailySales(yesterday);
if (yesterdaySales === 0) return null;
const changeRate = ((todaySales - yesterdaySales) / yesterdaySales) * 100;
// 变化超过30%视为异常
if (Math.abs(changeRate) > 30) {
return changeRate;
}
return null;
}
async getDailySales(date) {
const result = await SalesOrder.aggregate([
{
$match: {
orderDate: date,
status: { $ne: 'cancelled' }
}
},
{
$group: {
_id: null,
total: { $sum: '$totalAmount' }
}
}
]);
return result[0]?.total || 0;
}
}
数据导出功能
支持多种格式的报表导出:
// export/excel-export.js
class ExcelExporter {
// 导出销售报表
async exportSalesReport(params) {
const data = await SalesReportService.getSalesDetailReport(params);
const workbook = new Excel.Workbook();
const worksheet = workbook.addWorksheet('销售明细');
// 设置表头
worksheet.columns = [
{ header: '订单号', key: 'orderNo', width: 15 },
{ header: '日期', key: 'orderDate', width: 12 },
{ header: '客户', key: 'customerName', width: 20 },
{ header: '商品编码', key: 'productCode', width: 12 },
{ header: '商品名称', key: 'productName', width: 25 },
{ header: '数量', key: 'quantity', width: 10 },
{ header: '单价', key: 'unitPrice', width: 12 },
{ header: '金额', key: 'amount', width: 15 },
{ header: '利润', key: 'profit', width: 12 }
];
// 填充数据
worksheet.addRows(data.map(item => ({
...item,
amount: item.amount.toFixed(2),
profit: item.profit.toFixed(2)
})));
// 设置列格式
worksheet.getColumn('amount').numFmt = '¥#,##0.00';
worksheet.getColumn('profit').numFmt = '¥#,##0.00';
// 生成文件
const buffer = await workbook.xlsx.writeBuffer();
return buffer;
}
// 导出PDF报表
async exportPDFReport(reportType, params) {
const data = await this.getReportData(reportType, params);
const html = this.renderHTML(reportType, data);
// 使用puppeteer生成PDF
const pdf = await puppeteer.generatePDF(html);
return pdf;
}
}
移动端适配
经营分析模块需要适配移动端展示:
- 关键指标卡片:一目了然的KPI展示
- 简化图表:移动端使用更简洁的图表形式
- 数据筛选:支持日期范围、客户等筛选
- 导出分享:支持截图分享和PDF导出
总结
PSI系统的数据可视化与经营分析模块为企业提供了全面的数据分析能力。通过经营看板、趋势图表、多维度报表、智能预警等功能,帮助企业管理者实时掌握经营状况,做出科学决策。