个人编制软件展示

PSI - Purchase Sale Inventory 进销存软件

PSI进销存系统智能报表与经营分析

引言

进销存系统积累了大量的业务数据,如何从这些数据中提取有价值的信息,支撑企业经营决策,是报表分析模块的核心目标。本文将介绍智能报表与经营分析系统的设计与实现。

报表系统架构

整体报表系统架构:

层次 组件 职责
数据层 数据仓库、OLAP引擎 数据存储、多维分析
计算层 聚合计算、指标引擎 指标计算、实时统计
服务层 报表API、导出服务 数据接口、报表生成
展示层 图表组件、仪表盘 可视化展示、交互

指标引擎

统一指标定义与计算:

// 指标引擎
class MetricEngine {
  constructor() {
    this.metrics = new Map();
    this.registerDefaultMetrics();
  }

  // 注册指标
  registerMetric(config) {
    const metric = {
      id: config.id,
      name: config.name,
      type: config.type,
      category: config.category,
      formula: config.formula,
      aggregation: config.aggregation,
      filters: config.filters || [],
      timeWindow: config.timeWindow,
      dependencies: config.dependencies || []
    };

    this.metrics.set(config.id, metric);
    return this;
  }

  // 注册默认指标
  registerDefaultMetrics() {
    // 销售指标
    this.registerMetric({
      id: 'sales_amount',
      name: '销售额',
      type: 'basic',
      category: 'sales',
      aggregation: 'sum',
      formula: 'order_items.quantity * order_items.price'
    });

    this.registerMetric({
      id: 'sales_count',
      name: '订单数',
      type: 'basic',
      category: 'sales',
      aggregation: 'count',
      formula: 'orders.id'
    });

    this.registerMetric({
      id: 'sales_avg_order_value',
      name: '客单价',
      type: 'derived',
      category: 'sales',
      formula: 'sales_amount / sales_count'
    });

    // 库存指标
    this.registerMetric({
      id: 'inventory_value',
      name: '库存金额',
      type: 'computed',
      category: 'inventory',
      formula: 'SUM(inventory.quantity * product.cost_price)'
    });

    this.registerMetric({
      id: 'inventory_turnover',
      name: '库存周转率',
      type: 'ratio',
      category: 'inventory',
      formula: 'sales_cost / average_inventory'
    });

    // 利润指标
    this.registerMetric({
      id: 'gross_profit',
      name: '毛利润',
      type: 'derived',
      category: 'profit',
      formula: 'sales_amount - sales_cost'
    });

    this.registerMetric({
      id: 'gross_margin',
      name: '毛利率',
      type: 'ratio',
      category: 'profit',
      formula: 'gross_profit / sales_amount * 100'
    });
  }

  // 计算指标
  async calculate(metricId, params) {
    const metric = this.metrics.get(metricId);
    if (!metric) {
      throw new Error(`Metric not found: ${metricId}`);
    }

    const { startDate, endDate, filters, groupBy } = params;
    const query = this.buildQuery(metric, { startDate, endDate, filters, groupBy });
    const result = await this.executeQuery(query);

    return result;
  }

  // 构建 SQL 查询
  buildQuery(metric, params) {
    let sql = 'SELECT ';

    if (params.groupBy) {
      sql += `${params.groupBy}, `;
    }

    sql += `${metric.aggregation}(${metric.formula}) as value `;
    sql += 'FROM orders JOIN order_items ON orders.id = order_items.order_id ';
    sql += `WHERE orders.created_at BETWEEN '${params.startDate}' AND '${params.endDate}'`;

    if (params.filters) {
      for (const filter of params.filters) {
        sql += ` AND ${filter.field} ${filter.operator} ${filter.value}`;
      }
    }

    if (params.groupBy) {
      sql += ` GROUP BY ${params.groupBy}`;
    }

    return sql;
  }
}

多维分析引擎

支持灵活的维度分析与下钻:

// 多维分析引擎
class OLAPEngine {
  constructor() {
    this.dimensions = this.initDimensions();
    this.measures = this.initMeasures();
  }

  // 初始化维度
  initDimensions() {
    return {
      time: {
        name: '时间',
        hierarchy: ['year', 'quarter', 'month', 'week', 'day'],
        attributes: ['year', 'quarter', 'month', 'week', 'day']
      },
      product: {
        name: '商品',
        hierarchy: ['category', 'subcategory', 'product'],
        attributes: ['category', 'subcategory', 'product', 'brand']
      },
      customer: {
        name: '客户',
        hierarchy: ['customer_group', 'customer'],
        attributes: ['customer', 'customer_group', 'region']
      },
      warehouse: {
        name: '仓库',
        hierarchy: ['warehouse', 'location'],
        attributes: ['warehouse', 'location', 'region']
      }
    };
  }

  // 初始化度量
  initMeasures() {
    return [
      { id: 'sales_amount', name: '销售额', aggregator: 'SUM' },
      { id: 'sales_quantity', name: '销售数量', aggregator: 'SUM' },
      { id: 'order_count', name: '订单数', aggregator: 'COUNT' },
      { id: 'profit_amount', name: '利润', aggregator: 'SUM' }
    ];
  }

  // 多维查询
  async query(config) {
    const { measures, dimensions, filters, orderBy, limit } = config;

    // 验证维度
    for (const dim of dimensions) {
      if (!this.dimensions[dim]) {
        throw new Error(`Invalid dimension: ${dim}`);
      }
    }

    const sql = this.buildMDXQuery({ measures, dimensions, filters, orderBy, limit });
    return await this.executeQuery(sql);
  }

  // 构建查询
  buildMDXQuery({ measures, dimensions, filters, orderBy, limit }) {
    let sql = 'SELECT ';

    const selectParts = dimensions.map(d => d);
    for (const m of measures) {
      selectParts.push(`SUM(${m}) as ${m}`);
    }
    sql += selectParts.join(', ');

    const mainTable = this.detectMainTable(dimensions);
    sql += ` FROM ${mainTable}`;

    const joins = this.buildJoins(dimensions, measures);
    sql += joins;

    if (filters && filters.length > 0) {
      sql += ' WHERE ' + filters.map(f =>
        `${f.dimension}.${f.attribute} ${f.operator} '${f.value}'`
      ).join(' AND ');
    }

    sql += ` GROUP BY ${dimensions.join(', ')}`;

    if (orderBy) {
      sql += ` ORDER BY ${orderBy.field} ${orderBy.direction || 'DESC'}`;
    }

    if (limit) {
      sql += ` LIMIT ${limit}`;
    }

    return sql;
  }
}

报表生成器

动态生成各类报表:

// 报表生成器
class ReportGenerator {
  constructor(metricEngine, olapEngine) {
    this.metricEngine = metricEngine;
    this.olapEngine = olapEngine;
    this.templates = new Map();
    this.registerTemplates();
  }

  // 注册报表模板
  registerTemplates() {
    // 销售日报
    this.templates.set('daily_sales', {
      name: '销售日报',
      type: 'table',
      sections: [
        {
          title: '关键指标',
          metrics: ['sales_amount', 'sales_count', 'sales_avg_order_value']
        },
        {
          title: '销售趋势',
          type: 'chart',
          chartType: 'line',
          metrics: ['sales_amount'],
          dimensions: ['day']
        },
        {
          title: '热销商品',
          type: 'table',
          columns: ['product_name', 'quantity', 'amount']
        }
      ]
    });

    // 库存报表
    this.templates.set('inventory_status', {
      name: '库存状态报表',
      type: 'table',
      sections: [
        { title: '库存概览', metrics: ['inventory_value', 'inventory_quantity'] },
        { title: '库存明细', type: 'table', dimensions: ['product', 'warehouse'] }
      ]
    });
  }

  // 生成报表
  async generate(templateId, params) {
    const template = this.templates.get(templateId);
    if (!template) {
      throw new Error(`Template not found: ${templateId}`);
    }

    const result = {
      template: template.name,
      generatedAt: new Date().toISOString(),
      params,
      sections: []
    };

    for (const section of template.sections) {
      const sectionData = await this.generateSection(section, params);
      result.sections.push(sectionData);
    }

    return result;
  }

  // 生成报表区块
  async generateSection(section, params) {
    if (section.metrics) {
      const metrics = await this.metricEngine.calculateMultiple(
        section.metrics,
        params
      );
      return { title: section.title, type: section.type || 'metrics', data: metrics };
    }

    if (section.dimensions) {
      const data = await this.olapEngine.query({
        measures: section.measures || ['sales_amount'],
        dimensions: section.dimensions,
        filters: section.filters || [],
        limit: section.limit || 100
      });
      return { title: section.title, type: section.type || 'table', data };
    }

    return { title: section.title, type: section.type };
  }

  // 导出报表
  async export(report, format) {
    switch (format) {
      case 'excel': return this.exportToExcel(report);
      case 'pdf': return this.exportToPDF(report);
      case 'csv': return this.exportToCSV(report);
      default: throw new Error(`Unsupported format: ${format}`);
    }
  }
}

实时数据大屏

经营数据实时展示:

// 实时数据大屏
class Dashboard {
  constructor() {
    this.widgets = new Map();
    this.refreshInterval = 30000;
    this.timers = [];
  }

  // 添加组件
  addWidget(config) {
    const widget = {
      id: config.id,
      type: config.type,
      metric: config.metric,
      dimensions: config.dimensions,
      refreshInterval: config.refreshInterval || this.refreshInterval,
      config: config.config || {},
      element: null,
      lastData: null
    };

    this.widgets.set(config.id, widget);
    return widget;
  }

  // 启动实时更新
  start() {
    for (const [id, widget] of this.widgets) {
      this.refreshWidget(id);

      const timer = setInterval(() => {
        this.refreshWidget(id);
      }, widget.refreshInterval);

      this.timers.push(timer);
    }
  }

  // 刷新组件
  async refreshWidget(widgetId) {
    const widget = this.widgets.get(widgetId);
    if (!widget) return;

    try {
      const data = await this.fetchData(widget);
      widget.lastData = data;

      if (widget.element) {
        this.updateWidgetUI(widget, data);
      }
    } catch (error) {
      console.error(`Failed to refresh widget ${widgetId}:`, error);
    }
  }

  // 获取数据
  async fetchData(widget) {
    const params = {
      startDate: this.getRelativeDate(widget.config.period),
      endDate: new Date()
    };

    if (widget.metric) {
      return await metricEngine.calculate(widget.metric, params);
    }

    if (widget.dimensions) {
      return await olapEngine.query({
        ...params,
        measures: widget.config.measures,
        dimensions: widget.dimensions,
        limit: widget.config.limit
      });
    }
  }

  // 更新 UI
  updateWidgetUI(widget, data) {
    switch (widget.type) {
      case 'number': this.updateNumberWidget(widget, data); break;
      case 'chart': this.updateChartWidget(widget, data); break;
      case 'table': this.updateTableWidget(widget, data); break;
    }
  }

  // 更新数字组件
  updateNumberWidget(widget, data) {
    const element = widget.element;
    const value = element.querySelector('.value');
    const change = element.querySelector('.change');

    value.textContent = this.formatNumber(data.value);

    if (data.changePercent !== undefined) {
      change.textContent = `${data.changePercent > 0 ? '+' : ''}${data.changePercent}%`;
      change.className = `change ${data.changePercent >= 0 ? 'positive' : 'negative'}`;
    }
  }

  // 格式化数字
  formatNumber(num) {
    if (num >= 100000000) return (num / 100000000).toFixed(2) + '亿';
    if (num >= 10000) return (num / 10000).toFixed(2) + '万';
    return num.toFixed(2);
  }

  // 初始化大屏
  initialize(containerId) {
    const container = document.getElementById(containerId);
    const grid = document.createElement('div');
    grid.className = 'dashboard-grid';

    for (const [id, widget] of this.widgets) {
      const element = document.createElement('div');
      element.className = `dashboard-widget ${widget.type}`;
      element.innerHTML = this.createWidgetHTML(widget);
      grid.appendChild(element);
      widget.element = element;
    }

    container.appendChild(grid);
    this.start();
  }
}

智能预警

自动检测业务异常并提醒:

// 智能预警系统
class IntelligenceAlert {
  constructor() {
    this.rules = this.loadAlertRules();
    this.checkInterval = 300000; // 5分钟检查一次
  }

  // 加载预警规则
  loadAlertRules() {
    return [
      {
        id: 'low_stock',
        name: '库存不足预警',
        type: 'inventory',
        condition: async () => {
          const lowStock = await db.query(`
            SELECT COUNT(*) as count FROM inventory
            WHERE quantity <= min_stock
          `);
          return lowStock[0].count > 0;
        },
        severity: 'warning',
        channels: [' dashboard', 'sms', 'email']
      },
      {
        id: 'overdue_receivable',
        name: '应收账款逾期',
        type: 'finance',
        condition: async () => {
          const overdue = await db.query(`
            SELECT COUNT(*) as count FROM receivables
            WHERE due_date < NOW() AND status != 'paid'
          `);
          return overdue[0].count > 0;
        },
        severity: 'danger',
        channels: ['dashboard', 'email']
      },
      {
        id: 'sales_anomaly',
        name: '销售异常',
        type: 'sales',
        condition: async () => {
          const today = await this.getDailySales();
          const yesterday = await this.getDailySales(-1);
          const changeRate = ((today - yesterday) / yesterday) * 100;
          return Math.abs(changeRate) > 30;
        },
        severity: 'info',
        channels: ['dashboard']
      }
    ];
  }

  // 检查所有预警
  async checkAlerts() {
    const alerts = [];

    for (const rule of this.rules) {
      try {
        const isTriggered = await rule.condition();

        if (isTriggered) {
          alerts.push({
            ruleId: rule.id,
            name: rule.name,
            type: rule.type,
            severity: rule.severity,
            triggeredAt: new Date().toISOString()
          });
        }
      } catch (error) {
        console.error(`Error checking rule ${rule.id}:`, error);
      }
    }

    return alerts;
  }

  // 发送预警通知
  async sendNotification(alert, channels) {
    for (const channel of channels) {
      switch (channel) {
        case 'dashboard':
          await this.sendToDashboard(alert);
          break;
        case 'email':
          await this.sendEmailAlert(alert);
          break;
        case 'sms':
          await this.sendSMSAlert(alert);
          break;
      }
    }
  }

  // 启动预警监控
  startMonitoring() {
    setInterval(async () => {
      const alerts = await this.checkAlerts();

      for (const alert of alerts) {
        const rule = this.rules.find(r => r.id === alert.ruleId);
        await this.sendNotification(alert, rule.channels);
      }
    }, this.checkInterval);
  }
}

最佳实践建议

总结

智能报表与经营分析是企业数据化运营的核心支撑:

通过智能报表,企业可以更好地洞察业务现状,支撑科学决策。

← 下一篇:PSI进销存系统离线优先架构设计与实现