个人编制软件展示

PSI - Purchase Sale Inventory 进销存软件

PSI数据导入导出与Excel模板配置

数据导入导出概述

数据导入导出是进销存软件的核心功能之一,主要用于:初始化数据导入、批量修改数据、与其他系统数据对接、报表导出备份等场景。

支持的数据类型

数据类型 导入 导出 文件格式
商品档案 支持 支持 Excel/CSV
客户档案 支持 支持 Excel/CSV
供应商档案 支持 支持 Excel
库存数据 支持 支持 Excel
单据数据 部分支持 支持 Excel

商品档案导入示例

// 导入服务
class ImportService {
  constructor(ctx) {
    this.ctx = ctx;
  }

  // 解析 Excel 文件
  async parseExcel(filePath) {
    const XLSX = require('xlsx');
    const workbook = XLSX.readFile(filePath);
    const sheetName = workbook.SheetNames[0];
    const data = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName]);
    return data;
  }

  // 验证商品数据
  validateProduct(row, rowNum) {
    const errors = [];

    // 必填字段校验
    if (!row['商品编码']) {
      errors.push(`第${rowNum}行:商品编码不能为空`);
    }
    if (!row['商品名称']) {
      errors.push(`第${rowNum}行:商品名称不能为空`);
    }

    // 格式校验
    if (row['商品编码'] && !/^[A-Za-z0-9_-]+$/.test(row['商品编码'])) {
      errors.push(`第${rowNum}行:商品编码格式不正确`);
    }
    if (row['零售价'] && isNaN(parseFloat(row['零售价']))) {
      errors.push(`第${rowNum}行:零售价必须是数字`);
    }

    return errors;
  }

  // 导入商品档案
  async importProducts(filePath) {
    const rows = await this.parseExcel(filePath);
    const success = [];
    const errors = [];
    const existed = [];

    for (let i = 0; i < rows.length; i++) {
      const row = rows[i];
      const rowNum = i + 2; // Excel 行号(从2开始,1是表头)

      // 数据验证
      const validationErrors = this.validateProduct(row, rowNum);
      if (validationErrors.length > 0) {
        errors.push(...validationErrors);
        continue;
      }

      // 检查是否已存在
      const existProduct = await this.ctx.model.Product.findOne({
        productCode: row['商品编码']
      });

      if (existProduct) {
        existed.push(`第${rowNum}行:商品编码 ${row['商品编码']} 已存在`);
        continue;
      }

      // 创建商品
      const product = await this.ctx.model.Product.create({
        productCode: row['商品编码'],
        productName: row['商品名称'],
        category: row['商品分类'],
        unit: row['单位'],
        costPrice: parseFloat(row['成本价'] || 0),
        salePrice: parseFloat(row['零售价'] || 0),
        barCode: row['条码'],
        status: 'normal',
        createTime: new Date()
      });

      success.push(product);
    }

    return {
      total: rows.length,
      success: success.length,
      existed: existed.length,
      errors: [...errors, ...existed]
    };
  }
}

Excel 导出实现

// 导出服务
class ExportService {
  constructor(ctx) {
    this.ctx = ctx;
  }

  // 导出商品档案
  async exportProducts(filters = {}) {
    const XLSX = require('xlsx');

    // 查询数据
    const products = await this.ctx.model.Product.find(filters)
      .populate('category', 'categoryName')
      .lean();

    // 转换数据格式
    const data = products.map(p => ({
      '商品编码': p.productCode,
      '商品名称': p.productName,
      '商品分类': p.category?.categoryName || '',
      '单位': p.unit,
      '成本价': p.costPrice,
      '零售价': p.salePrice,
      '批发价': p.wholesalePrice,
      '条码': p.barCode,
      '商品规格': p.spec,
      '状态': p.status === 'normal' ? '正常' : '停用',
      '创建日期': this.formatDate(p.createTime)
    }));

    // 创建工作表
    const worksheet = XLSX.utils.json_to_sheet(data);

    // 设置列宽
    worksheet['!cols'] = [
      { wch: 15 }, // 商品编码
      { wch: 30 }, // 商品名称
      { wch: 15 }, // 分类
      { wch: 8 },  // 单位
      { wch: 12 }, // 成本价
      { wch: 12 }, // 零售价
      { wch: 12 }, // 批发价
      { wch: 20 }, // 条码
      { wch: 20 }, // 规格
      { wch: 8 },  // 状态
      { wch: 15 }  // 创建日期
    ];

    // 创建工作簿
    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, worksheet, '商品档案');

    // 生成文件
    const exportDir = path.join(__dirname, 'exports');
    if (!fs.existsSync(exportDir)) {
      fs.mkdirSync(exportDir, { recursive: true });
    }

    const filename = `商品档案_${this.formatDate(new Date(), 'YYYYMMDDHHmmss')}.xlsx`;
    const filePath = path.join(exportDir, filename);

    XLSX.writeFile(workbook, filePath);

    return {
      filename,
      filePath,
      recordCount: data.length
    };
  }

  // 导出库存数据
  async exportInventory(warehouseId) {
    const XLSX = require('xlsx');

    const query = {};
    if (warehouseId) {
      query.warehouseId = warehouseId;
    }

    const inventory = await this.ctx.model.Inventory.find(query)
      .populate('productId', 'productCode productName unit')
      .populate('warehouseId', 'warehouseName')
      .lean();

    const data = inventory.map(inv => ({
      '仓库': inv.warehouseId?.warehouseName || '',
      '商品编码': inv.productId?.productCode || '',
      '商品名称': inv.productId?.productName || '',
      '单位': inv.productId?.unit || '',
      '库存数量': inv.quantity,
      '锁定数量': inv.lockedQuantity,
      '可用数量': inv.quantity - inv.lockedQuantity,
      '成本金额': (inv.quantity * inv.costPrice).toFixed(2),
      '零售金额': (inv.quantity * inv.salePrice).toFixed(2)
    }));

    // 计算合计行
    const totalQuantity = data.reduce((sum, item) => sum + item['库存数量'], 0);
    data.push({
      '仓库': '合计',
      '商品编码': '',
      '商品名称': '',
      '单位': '',
      '库存数量': totalQuantity,
      '锁定数量': '',
      '可用数量': '',
      '成本金额': data.reduce((sum, item) => sum + parseFloat(item['成本金额'] || 0), 0).toFixed(2),
      '零售金额': data.reduce((sum, item) => sum + parseFloat(item['零售金额'] || 0), 0).toFixed(2)
    });

    const worksheet = XLSX.utils.json_to_sheet(data);
    worksheet['!cols'] = [
      { wch: 15 }, { wch: 15 }, { wch: 30 }, { wch: 8 },
      { wch: 12 }, { wch: 12 }, { wch: 12 }, { wch: 15 }, { wch: 15 }
    ];

    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, worksheet, '库存数据');

    const filename = `库存数据_${this.formatDate(new Date(), 'YYYYMMDDHHmmss')}.xlsx`;
    const filePath = path.join(__dirname, 'exports', filename);

    XLSX.writeFile(workbook, filePath);
    return { filename, filePath, recordCount: data.length };
  }

  formatDate(date, format = 'YYYY-MM-DD') {
    if (!date) return '';
    const d = new Date(date);
    const year = d.getFullYear();
    const month = String(d.getMonth() + 1).padStart(2, '0');
    const day = String(d.getDate()).padStart(2, '0');
    const hours = String(d.getHours()).padStart(2, '0');
    const minutes = String(d.getMinutes()).padStart(2, '0');
    const seconds = String(d.getSeconds()).padStart(2, '0');

    return format
      .replace('YYYY', year)
      .replace('MM', month)
      .replace('DD', day)
      .replace('HH', hours)
      .replace('mm', minutes)
      .replace('ss', seconds);
  }
}

导入模板配置

系统默认提供标准导入模板,可根据需要自定义字段映射:

商品导入模板字段说明

字段名称 是否必填 说明 示例
商品编码 唯一标识 P001
商品名称 商品全称 iPhone 15 Pro
商品分类 分类名称 电子产品
单位 默认:个 台/箱/个
成本价 数字 5000
零售价 数字 5999
条码 商品条码 6901234567890

导入导出使用流程

导入操作步骤

  1. 在系统中下载标准导入模板
  2. 按照模板格式填写数据
  3. 进入数据导入页面,选择导入类型
  4. 上传 Excel 文件
  5. 系统自动校验数据,显示预览
  6. 确认无误后执行导入
  7. 查看导入结果报告

导出操作步骤

  1. 进入数据导出页面
  2. 选择要导出的数据类型
  3. 设置筛选条件(如日期范围、仓库等)
  4. 点击导出按钮
  5. 系统生成 Excel 文件并下载

注意事项

总结

数据导入导出功能可以大大提高日常工作效率,特别是在初始化阶段和批量数据处理时。注意定期备份数据,导入操作建议先在小数据集上测试验证。

← 下一篇:PSI 多门店管理与数据同步方案