首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >用 AI 助手 WorkBuddy 10 天开发一套进销存软件—全程实录

用 AI 助手 WorkBuddy 10 天开发一套进销存软件—全程实录

原创
作者头像
用户1421032
修改2026-05-13 18:23:55
修改2026-05-13 18:23:55
2191
举报

前言

我是一名兼任财务与产品管理的工程公司员工,负责多个工地项目的农民工工资及材料采购管理。每个月我都要手工录入几十张采购发票和销售清单,再对着 Excel 逐条核账——不仅费时,还容易出错。

我开始尝试用 WorkBuddy(一款 AI 编程助手)来帮我开发一套专属进销存系统,最终在约 5天内完成了一套可以实际上线使用的软件。整个过程我几乎没有自己写过一行代码,但我需要非常清楚地告诉 WorkBuddy 我想要什么、为什么要这样设计。

本文是这段经历的完整复盘,希望对同样有"业务强、代码弱"困境的朋友有所帮助。

一、项目背景与需求分析

1.1 痛点梳理

在动手之前,我先把业务痛点整理成一张清单:

痛点

现状

期望

发票录入

人工手打,每张约 5 分钟

OCR 自动识别,1 秒导入

库存核对

Excel 手工加减,易出错

系统自动计算,支持负库存提醒

多项目管理

多个文件夹分公司,容易混淆

单系统多公司切换

出库成本

不清楚每次出库真实成本

加权平均法自动计算

1.2 功能模块规划

代码语言:txt
复制
进销存软件
├── 基础档案
│   ├── 商品管理(含规格/单位)
│   ├── 供应商管理
│   └── 客户管理
├── 采购管理
│   ├── OCR 扫描进项发票
│   └── 发票明细入库
├── 销售管理
│   ├── OCR 扫描销项发票
│   └── 发票明细出库(含出库成本)
├── 库存管理
│   ├── 期初库存导入(Excel)
│   └── 库存台账(数量 + 金额合计)
└── 系统设置
    ├── 多公司管理
    └── 用户权限(JWT 鉴权)

二、技术选型——和 WorkBuddy 一起决策

我把上面的需求描述告诉 WorkBuddy,然后问它:"给我推荐一个适合 Windows 本地部署、Python 开发者友好、前后端分离的技术栈。"

WorkBuddy 给出了以下方案,并解释了每个选择的理由:

2.1 技术栈总览

层次

技术

选择理由

后端

Flask (Python)

轻量、易扩展,适合中小型应用;Python 生态丰富(OCR 库多)

数据库

MySQL 8.0

事务支持好,ENUM 类型方便状态管理

ORM/驱动

PyMySQL(原生 SQL)

不用 ORM 避免复杂映射,SQL 清晰可读

前端

Vue 3 + Vite

组合式 API 上手快,Vite 构建速度快

UI 组件库

Element Plus

表单/表格组件丰富,契合后台系统风格

状态管理

Pinia

比 Vuex 更简洁

OCR 引擎

百度云 OCR + 腾讯云 OCR 双引擎

互为备用,识别率更高

鉴权

JWT (JSON Web Token)

无状态鉴权,前后端分离友好

部署

本地 Windows 服务,start_backend.py 一键启动

无需运维经验

2.2 数据库设计思路

WorkBuddy 帮我设计了核心表结构,关键决策包括:

  • inventory_transactions 表使用 ENUM('purchase','sale','initial','adjustment')reference_type 字段,统一管理所有出入库记录
  • 采购和销售发票各自独立建表(purchase_invoicessales_invoices),通过外键关联到明细表
  • 库存余量通过 inventory_transactions 聚合计算,不冗余存储,保证数据一致性

核心设计原则(WorkBuddy 建议):库存数量和金额不单独存字段,每次查询都从流水表实时聚合——这样历史数据永远可追溯。

三、开发过程——WorkBuddy 如何帮我写代码

3.1 项目初始化

我给 WorkBuddy 发了第一条指令:

代码语言:txt
复制
帮我在 D:\workbuddy\inventory-system 目录下初始化一个
Flask + Vue3 前后端分离项目,后端端口 5000,前端端口 5173,
MySQL 数据库名 inventory_db。

WorkBuddy 在几分钟内完成了:

  • 创建目录结构(backend/frontend/
  • 生成 requirements.txt,包含 Flask、PyMySQL、PyJWT、python-dotenv 等依赖
  • 初始化 create_tables.py,一键建库建表
  • 生成 Vue3 项目脚手架(Vite + Element Plus + Pinia)

实际项目目录结构:

代码语言:txt
复制
inventory-system/
├── backend/
│   ├── app.py              # Flask 主入口
│   ├── start_backend.py    # 一键启动脚本
│   ├── config.py           # 数据库/JWT 配置
│   ├── routes/
│   │   ├── auth.py         # 登录鉴权
│   │   ├── inventory.py    # 库存管理(含期初导入)
│   │   ├── purchase.py     # 采购发票
│   │   ├── sales.py        # 销售发票
│   │   ├── products.py     # 商品档案
│   │   ├── suppliers.py    # 供应商
│   │   └── customers.py    # 客户
│   ├── utils/
│   │   ├── baidu_ocr.py    # 百度 OCR 封装
│   │   └── tencent_ocr.py  # 腾讯 OCR 封装
│   └── requirements.txt
├── frontend/
│   ├── src/
│   │   ├── views/          # 14 个页面组件
│   │   ├── router/         # Vue Router 路由配置
│   │   ├── stores/         # Pinia 状态管理
│   │   └── api/            # Axios API 模块
│   └── package.json
└── .workbuddy/             # WorkBuddy 工作目录

3.2 后端 API 开发

登录鉴权(JWT)
代码语言:python
复制
# routes/auth.py(WorkBuddy 生成)
@auth_bp.route('/api/auth/login', methods=['POST'])
def login():
    data = request.json
    username = data.get('username')
    password = data.get('password')

    user = db.execute_one(
        "SELECT * FROM users WHERE username = %s AND password = %s",
        (username, md5(password))
    )
    if not user:
        return jsonify({'code': 401, 'msg': '用户名或密码错误'}), 401

    token = jwt.encode({
        'user_id': user['id'],
        'exp': datetime.utcnow() + timedelta(hours=24)
    }, SECRET_KEY, algorithm='HS256')

    return jsonify({'code': 0, 'token': token, 'user': user})
采购发票入库(核心逻辑)

WorkBuddy 帮我实现了采购发票的"幂等性入库"——同一张发票号不会重复入库:

代码语言:python
复制
@purchase_bp.route('/api/purchase/invoices', methods=['POST'])
@require_auth
def create_purchase_invoice():
    data = request.json
    company_id = g.company_id  # 从 JWT 中取多公司 ID

    # 幂等检查:发票号唯一
    existing = db.execute_one(
        "SELECT id FROM purchase_invoices WHERE invoice_no = %s AND company_id = %s",
        (data['invoice_no'], company_id)
    )
    if existing:
        return jsonify({'code': 400, 'msg': '该发票号已存在'}), 400

    # 事务入库
    with db.transaction():
        invoice_id = db.insert('purchase_invoices', {...})
        for item in data['items']:
            db.insert('purchase_invoice_items', {
                'invoice_id': invoice_id, ...
            })
            # 写入库存流水
            db.insert('inventory_transactions', {
                'reference_type': 'purchase',
                'reference_id': invoice_id,
                'product_id': item['product_id'],
                'quantity': item['quantity'],
                'unit_price': item['unit_price'],
                'company_id': company_id
            })

    return jsonify({'code': 0, 'msg': '入库成功'})

3.3 OCR 发票识别——最有趣的部分

这是整个项目最有技术含量的部分。我告诉 WorkBuddy:

代码语言:txt
复制
我需要上传一张发票图片,系统自动识别出:
- 发票号、开票日期
- 供应商名称和税号
- 商品明细列表(名称、规格、数量、单价、金额)

百度 OCR 和腾讯 OCR 各自的 API Key 我已经申请好了。
请帮我封装两个 OCR 函数,优先用百度,失败时自动切换腾讯。

WorkBuddy 生成了以下核心逻辑:

代码语言:python
复制
# utils/baidu_ocr.py
def recognize_invoice(image_path: str) -> dict:
    """调用百度增值税发票识别 API"""
    client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
    with open(image_path, 'rb') as f:
        image_data = f.read()

    result = client.vatInvoice(image_data)

    if 'words_result' not in result:
        raise OCRException(f"百度 OCR 识别失败: {result}")

    # 结构化解析
    words = result['words_result']
    items = []
    for item in words.get('CommodityName', {}).get('word', []):
        items.append({
            'name': item,
            'quantity': ...,
            'unit_price': ...,
            'amount': ...
        })

    return {
        'invoice_no': words.get('InvoiceNum', {}).get('word', ''),
        'date': words.get('InvoiceDate', {}).get('word', ''),
        'seller_name': words.get('SellerName', {}).get('word', ''),
        'seller_tax_no': words.get('SellerRegisterNum', {}).get('word', ''),
        'items': items,
        'total_amount': words.get('TotalAmount', {}).get('word', '0')
    }

双引擎降级策略:

代码语言:python
复制
def ocr_invoice_with_fallback(image_path: str) -> dict:
    """优先百度,失败切换腾讯"""
    try:
        return baidu_ocr.recognize_invoice(image_path)
    except Exception as e:
        logger.warning(f"百度 OCR 失败,切换腾讯: {e}")
        return tencent_ocr.recognize_invoice(image_path)

3.4 期初库存导入(Excel 批量导入)

财务系统上线前需要导入历史库存数据。我提供了一个 Excel 模板,WorkBuddy 帮我实现了完整的导入流程:

代码语言:python
复制
@inventory_bp.route('/api/inventory/initial-import', methods=['POST'])
@require_auth
def initial_import():
    """Excel 期初库存导入"""
    file = request.files.get('file')
    if not file:
        return jsonify({'code': 400, 'msg': '请上传文件'}), 400

    # 检查是否已有期初数据(防重复导入)
    existing = db.execute_one(
        "SELECT COUNT(*) as cnt FROM inventory_transactions "
        "WHERE reference_type = 'initial' AND company_id = %s",
        (g.company_id,)
    )
    if existing['cnt'] > 0:
        return jsonify({'code': 400, 'msg': '期初库存已导入,如需重新导入请先清除'}), 400

    # 用 openpyxl 解析 Excel
    wb = openpyxl.load_workbook(file)
    ws = wb.active

    success_count = 0
    errors = []

    with db.transaction():
        for row_num, row in enumerate(ws.iter_rows(min_row=2, values_only=True), start=2):
            product_name, spec, unit, quantity, unit_price = row[:5]

            if not product_name or quantity is None:
                continue

            try:
                # 模糊匹配商品档案
                product = find_or_create_product(product_name, spec, unit)

                db.insert('inventory_transactions', {
                    'reference_type': 'initial',
                    'product_id': product['id'],
                    'quantity': float(quantity),
                    'unit_price': float(unit_price or 0),
                    'company_id': g.company_id,
                    'remark': '期初导入'
                })
                success_count += 1
            except Exception as e:
                errors.append(f"第 {row_num} 行:{str(e)}")

    return jsonify({
        'code': 0,
        'success_count': success_count,
        'errors': errors
    })

3.5 前端开发——14 个 Vue3 页面

前端部分我让 WorkBuddy 并行开发,把 14 个页面分批次生成。关键页面包括:

采购发票页面(OCR 核心交互):

代码语言:txt
复制
<!-- PurchaseForm.vue 核心部分 -->
<template>
  <div class="purchase-form">
    <!-- 发票上传区域 -->
    <el-upload
      action="/api/ocr/invoice"
      :headers="authHeaders"
      :on-success="handleOCRSuccess"
      accept=".jpg,.png,.pdf"
      drag
    >
      <el-icon class="el-icon--upload"><upload-filled /></el-icon>
      <div class="el-upload__text">拖拽发票图片到此处,或 <em>点击上传</em></div>
    </el-upload>

    <!-- OCR 识别结果(可编辑) -->
    <el-form v-if="invoiceData" :model="invoiceData" label-width="120px">
      <el-form-item label="发票号码">
        <el-input v-model="invoiceData.invoice_no" />
      </el-form-item>
      <el-form-item label="供应商">
        <el-select v-model="invoiceData.supplier_id" filterable allow-create>
          <el-option v-for="s in suppliers" :key="s.id" :label="s.name" :value="s.id" />
        </el-select>
      </el-form-item>

      <!-- 商品明细表格(可编辑) -->
      <el-table :data="invoiceData.items" border>
        <el-table-column label="商品名称">
          <template #default="{ row }">
            <el-input v-model="row.product_name" @blur="handleFuzzyMatch(row)" />
          </template>
        </el-table-column>
        <!-- ... 数量、单价、金额列 -->
      </el-table>

      <el-button type="primary" @click="submitInvoice">确认入库</el-button>
    </el-form>
  </div>
</template>

库存台账页面(带汇总行):

代码语言:txt
复制
<!-- InventoryLedger.vue 核心查询逻辑 -->
<script setup>
const ledgerData = ref([])
const totalQty = computed(() => ledgerData.value.reduce((s, r) => s + r.quantity, 0))
const totalAmount = computed(() => ledgerData.value.reduce((s, r) => s + r.amount, 0))

// 查询库存台账
const fetchLedger = async () => {
  const res = await api.get('/api/inventory/ledger', {
    params: { company_id: companyStore.currentCompanyId, ...filters }
  })
  ledgerData.value = res.data.list
}
</script>

四、商品模糊匹配——解决 OCR 识别差异

这是一个让我印象深刻的细节。OCR 识别出来的商品名称常常和档案里的不完全一致:

  • OCR 识别:"钢筋 HRB400 Φ16mm"
  • 档案存的:"螺纹钢筋 HRB400 16"

我告诉 WorkBuddy:

代码语言:txt
复制
商品名称模糊匹配规则:
1. 名称中包含相同核心词(如"钢筋")
2. 规格相同(如"HRB400"、"16mm")
3. 单位相同(如"吨")
以上三个条件满足则视为同一商品,自动匹配并关联

WorkBuddy 生成了一个批量模糊匹配 API:

代码语言:python
复制
@inventory_bp.route('/api/inventory/batch-fuzzy-match', methods=['POST'])
@require_auth
def batch_fuzzy_match():
    """OCR 识别后批量模糊匹配商品"""
    items = request.json.get('items', [])
    products = db.execute_many(
        "SELECT id, name, spec, unit FROM products WHERE company_id = %s",
        (g.company_id,)
    )

    results = []
    for item in items:
        matched = None
        best_score = 0

        for product in products:
            score = 0
            # 名称包含匹配(核心词提取)
            if extract_core_name(item['name']) in product['name']:
                score += 2
            # 规格完全匹配
            if normalize_spec(item.get('spec', '')) == normalize_spec(product['spec']):
                score += 2
            # 单位匹配
            if item.get('unit', '') == product['unit']:
                score += 1

            if score > best_score and score >= 3:
                best_score = score
                matched = product

        results.append({
            'input': item,
            'matched_product': matched,
            'confidence': best_score / 5
        })

    return jsonify({'code': 0, 'results': results})

五、遇到的坑与解决方案

坑 1:MySQL 8 的 ENUM 类型迁移问题

在后期新增 initial(期初)类型时,直接 ALTER TABLE 修改 ENUM 失败。

WorkBuddy 的解决方案:

代码语言:sql
复制
-- 先修改为 VARCHAR,再约束
ALTER TABLE inventory_transactions
  MODIFY COLUMN reference_type VARCHAR(20) NOT NULL;

-- 添加 CHECK 约束
ALTER TABLE inventory_transactions
  ADD CONSTRAINT chk_ref_type
  CHECK (reference_type IN ('purchase', 'sale', 'initial', 'adjustment'));

坑 2:期初导入金额与实际不符

导入了 1,818,830.55 元,但账面显示 1,705,583.95 元,相差约 11 万。

排查过程:

  1. 让 WorkBuddy 写了对比查询 SQL,找出差异商品
  2. 发现部分 Excel 行的"含税单价"被当成"不含税单价"处理
  3. 修正了单价字段的取列逻辑

教训: 期初导入一定要在导入后立刻跑对账 SQL 验证总金额。

坑 3:负库存的业务决策

有些商品因为期初录入不完整,出库时会出现负库存。

我们的策略: 对于工程类材料,允许负库存放行(不阻断业务),但在台账中用红色标注,月底统一核查。

WorkBuddy 在 API 层实现了这个逻辑:

代码语言:python
复制
# 负库存放行策略
def check_stock_before_sale(product_id, quantity, company_id):
    current_stock = get_current_stock(product_id, company_id)
    if current_stock < quantity:
        # 不阻断,记录警告
        logger.warning(f"商品 {product_id} 库存不足: 当前 {current_stock},出库 {quantity}")
        return {'allow': True, 'warning': f'库存不足,当前余量 {current_stock}'}
    return {'allow': True, 'warning': None}

六、系统实际效果

主要功能页面

经过约 10 天的开发与调试,系统实现了以下核心功能:

  1. 登录与多公司切换 - JWT 鉴权,一套系统管理多个项目公司
  2. OCR 发票识别 - 拍照上传,秒级识别,识别率 > 95%
  3. 采购入库 - 发票明细自动入库,商品档案自动匹配
  4. 销售出库 - 含出库成本计算(加权平均法)
  5. 期初库存导入 - Excel 模板批量导入历史数据
  6. 库存台账 - 实时余量,数量+金额双汇总

效率对比

操作

原来耗时

现在耗时

提升

录入一张采购发票

5 分钟

30 秒

10 倍

月度库存核对

半天

10 分钟

30 倍

多公司账务切换

人工切换文件

显著提升

显著提升

七、WorkBuddy 使用心得

7.1 WorkBuddy 擅长什么

  • 快速搭建项目骨架 — 给出技术栈,几分钟内生成完整目录和配置
  • 并行开发多个模块 — 可以同时让它开发前端多个页面,不互相干扰
  • 解释设计决策 — 每段代码都附带解释,帮助理解而不只是执行
  • 主动发现问题 — 比如它主动提醒我"期初导入要做幂等检查"

7.2 如何更高效地使用

1. 描述问题而不是描述代码

差的指令:

代码语言:txt
复制
写一个 Python 函数 insert_into_db(data)

好的指令:

代码语言:txt
复制
我需要在采购发票保存时,把每条商品明细同时写入库存流水表,
要求事务原子性,如果任何一条失败则全部回滚,
数据库是 MySQL 8,用 PyMySQL 连接池。

2. 给出业务约束而不是技术约束

差的指令:

代码语言:txt
复制
添加一个 if 判断

好的指令:

代码语言:txt
复制
同一张发票号,在同一个公司下只能录入一次,
如果重复提交要返回友好的错误提示(不是 500 报错)

3. 分阶段迭代,不要一次要求太多

先让系统能跑起来,再逐步加功能。每次只描述当前最重要的那一个需求。

7.3 WorkBuddy 做不了什么

  • 理解隐含的业务规则 — 它不知道你们行业的特殊规定,需要你明确说明
  • 替代测试 — 生成的代码有时有小 bug,一定要自己测试验证
  • 做最终决策 — 涉及数据安全、架构选型,最终还是要自己判断

八、完整部署指南

8.1 环境准备

代码语言:bash
复制
# 安装 Python 依赖
pip install flask pymysql pyjwt python-dotenv openpyxl aip

# 创建 MySQL 数据库
mysql -u root -p
CREATE DATABASE inventory_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

# 初始化表结构
python backend/create_tables.py

8.2 配置文件

代码语言:python
复制
# backend/config.py
DB_CONFIG = {
    'host': '127.0.0.1',
    'port': 3306,
    'user': 'root',
    'password': 'your_password',
    'database': 'inventory_db',
    'charset': 'utf8mb4'
}

JWT_SECRET = 'your-secret-key-change-in-production'
BAIDU_OCR_APP_ID = 'your_app_id'
BAIDU_OCR_API_KEY = 'your_api_key'
BAIDU_OCR_SECRET_KEY = 'your_secret_key'

8.3 一键启动

代码语言:python
复制
# start_backend.py — WorkBuddy 生成的启动脚本
import subprocess
import sys
import os

os.chdir(os.path.dirname(os.path.abspath(__file__)))

print("启动后端服务...")
process = subprocess.Popen(
    [sys.executable, 'app.py'],
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT
)

print(f"后端服务已启动,PID: {process.pid}")
print("访问地址:http://127.0.0.1:5000")
代码语言:bash
复制
# 启动前端
cd frontend && npm run dev

# 访问系统
open http://localhost:5173

九、总结

这次用 WorkBuddy 开发进销存系统的经历,改变了我对"普通人能不能做软件开发"这个问题的看法。

我的收获:

  1. AI 降低了技术门槛,但没有降低思维门槛 — 你仍然需要清晰地分析业务流程、定义数据结构、设计异常处理逻辑。
  2. 业务理解 > 编程能力 — 我的优势在于了解财务业务,WorkBuddy 的优势在于写代码。两者结合,才能做出真正好用的系统。
  3. 迭代是关键 — 没有一次就做对的系统。期初金额差异、OCR 识别偏差、负库存策略……这些都是在实际使用中发现并修正的。

如果你也有一个"一直想做但不会写代码"的业务系统,不妨试试 WorkBuddy。你需要的不是会写代码,而是清晰地知道自己想要什么

附录:核心 API 文档

接口

方法

说明

/api/auth/login

POST

用户登录,返回 JWT Token

/api/purchase/invoices

POST

创建采购发票并入库

/api/sales/invoices

POST

创建销售发票并出库

/api/inventory/initial-import

POST

Excel 期初库存导入

/api/inventory/initial-status

GET

查询期初导入状态

/api/inventory/initial-template

GET

下载期初导入模板

/api/inventory/ledger

GET

查询库存台账(含合计)

/api/inventory/batch-fuzzy-match

POST

OCR 商品批量模糊匹配

/api/products

GET/POST

商品档案管理

/api/suppliers

GET/POST

供应商管理

/api/customers

GET/POST

客户管理

本文代码均为示意,实际项目中请注意:生产环境 JWT Secret 须使用强随机字符串,数据库密码不要硬编码在代码中,建议使用 .env 文件管理敏感配置。

如有问题欢迎在评论区交流,一起探讨 AI 辅助开发的更多可能。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、项目背景与需求分析
    • 1.1 痛点梳理
    • 1.2 功能模块规划
  • 二、技术选型——和 WorkBuddy 一起决策
    • 2.1 技术栈总览
    • 2.2 数据库设计思路
  • 三、开发过程——WorkBuddy 如何帮我写代码
    • 3.1 项目初始化
    • 3.2 后端 API 开发
      • 登录鉴权(JWT)
      • 采购发票入库(核心逻辑)
    • 3.3 OCR 发票识别——最有趣的部分
    • 3.4 期初库存导入(Excel 批量导入)
    • 3.5 前端开发——14 个 Vue3 页面
  • 四、商品模糊匹配——解决 OCR 识别差异
  • 五、遇到的坑与解决方案
    • 坑 1:MySQL 8 的 ENUM 类型迁移问题
    • 坑 2:期初导入金额与实际不符
    • 坑 3:负库存的业务决策
  • 六、系统实际效果
    • 主要功能页面
    • 效率对比
  • 七、WorkBuddy 使用心得
    • 7.1 WorkBuddy 擅长什么
    • 7.2 如何更高效地使用
    • 7.3 WorkBuddy 做不了什么
  • 八、完整部署指南
    • 8.1 环境准备
    • 8.2 配置文件
    • 8.3 一键启动
  • 九、总结
  • 附录:核心 API 文档
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档