今宽进销存系统 · 系统开发说明书

进销存管理系统 · 系统开发说明书

系统开发说明书 编制日期:2026-05-25 版本:v3.0 编制单位:铭见(上海)智能科技有限公司

版本:v3.0 | 日期:2026-05-25 | 编写:铭见(上海)智能科技有限公司
适用:Phase 1-4 全阶段开发 | 目标数据库:PostgreSQL 16

目录
  1. 系统概述
  2. 数据库设计
  3. API 设计
  4. 前端页面规划
  5. 二维码集成方案
  6. AI 识别集成方案
  7. 开发规范
  8. 附录:DDL 完整脚本

1. 系统概述

1.1 项目目标

为安徽今宽新材料科技有限公司开发一套 订单 → 到货 → 分切 → 出库 全流程覆盖的进销存管理系统,实现:

1.2 技术栈
技术 备注
后端框架 Python Flask 已有 Phase 0 代码复用
ORM Flask-SQLAlchemy PostgreSQL 适配
数据库 PostgreSQL 16 已有运行实例
前端 Vue 3 + Vite 已有 Phase 0 原型
UI 组件 Element Plus 企业级组件库
状态管理 Pinia
路由 Vue Router
HTTP 客户端 Axios JWT Token 拦截
认证 Flask-JWT-Extended 24h 过期
OCR 腾讯云 GeneralBasicOCR 已集成至 Phase 0
部署 Nginx + Gunicorn 阿里云 ECS
二维码 Python qrcode + pyzbar 生成 + 扫码解析

1.3 与 Phase 0 的继承关系

Phase 0(现有)已完成以下模块: - 腾讯云 OCR 集成(tencent_ocr.py) - 本地规格解析引擎(spec_parser.py)— 支持 T/W 前缀、三维/二维规格、材质/硬度提取 - 客户模板匹配(template_matcher.py) - AI 识别编排流程(app.py + recognizer.py) - 前端原型(HTML 模拟页面)

本说明书覆盖 Phase 1-4 全阶段开发内容: - 完整的数据库设计(13 张核心业务表) - RESTful API 层(Blueprints 模块化) - 进销存全流程(入库 → 库存 → 加工 → 出库 → 订单跟踪) - 二维码标签生成与扫码(Phase 3) - 前端 Vue3 SPA

各 Phase 对应的功能模块详见下表:

Phase 对应 API 模块 对应前端页面
Phase 1 auth / customers / suppliers / orders / inbound / inventory / reports 登录 / 仪表盘 / 订单管理 / 入库管理 / 库存台账 / 客户/供应商管理 / 报表
Phase 2 processing / outbound 加工管理 / 出库管理
Phase 3 qr 二维码管理
Phase 4 reports(扩展分析) 经营分析仪表盘

2. 数据库设计

2.1 ER 关系总览
┌───────────┐     ┌───────────────────┐     ┌───────────┐
│ customers │────→│     orders        │←────│ suppliers │
└───────────┘     │ (订单)            │     └───────────┘
      │           │  │   order_items   │           │
      │           │  │ (订单明细)       │           │
      │           └──┴─────────────────┘           │
      │                                             │
      ▼                                             ▼
┌───────────┐     ┌───────────┐     ┌───────────────┐
│  outbound │     │ inventory │     │  inbound       │
│  (出库)    │←────│ (库存)    │────→│  (入库)        │
│   │       │     └───────────┘     │   │           │
│   │ 出库明细│        │ 加工单      │ 入库明细       │
│   ▼       │        ▼             │   ▼           │
│ outbound  │    processing        │ inbound        │
│ _items    │    _orders           │ _items         │
└───────────┘     └───────────┘     └───────────────┘
                       │
                       ▼
                 ┌───────────┐
                 │ qr_labels │
                 │ (二维码)   │
                 └───────────┘

                  ┌───────────┐
                  │   users   │
                  └───────────┘

2.2 客户表 (customers)
CREATE TABLE customers (
    id            BIGSERIAL       PRIMARY KEY,
    code          VARCHAR(32)     UNIQUE NOT NULL,       -- 客户编号
    name          VARCHAR(200)    NOT NULL,               -- 客户名称
    contact       VARCHAR(100),                           -- 联系人
    phone         VARCHAR(32),                            -- 联系电话
    address       TEXT,                                   -- 地址
    credit_terms  VARCHAR(64),                            -- 结算方式/账期
    remark        TEXT,                                   -- 备注
    status        VARCHAR(16)     NOT NULL DEFAULT 'active',  -- active/disabled
    created_at    TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMP       NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_customers_code ON customers(code);
CREATE INDEX idx_customers_status ON customers(status);

2.3 供应商表 (suppliers)
CREATE TABLE suppliers (
    id             BIGSERIAL       PRIMARY KEY,
    code           VARCHAR(32)     UNIQUE NOT NULL,      -- 供应商编号
    name           VARCHAR(200)    NOT NULL,              -- 供应商名称
    contact        VARCHAR(100),                          -- 联系人
    phone          VARCHAR(32),                           -- 联系电话
    material_scope VARCHAR(200),                          -- 供应品类范围
    remark         TEXT,                                  -- 备注
    status         VARCHAR(16)     NOT NULL DEFAULT 'active',  -- active/disabled
    created_at     TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at     TIMESTAMP       NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_suppliers_code ON suppliers(code);
CREATE INDEX idx_suppliers_status ON suppliers(status);

2.4 订单表 (orders)
CREATE TABLE orders (
    id               BIGSERIAL       PRIMARY KEY,
    order_no         VARCHAR(64)     UNIQUE NOT NULL,    -- 系统单号(自动生成)
    customer_po_no   VARCHAR(128),                       -- 客户 PO 号
    customer_id      BIGINT          NOT NULL             -- 客户ID
                     REFERENCES customers(id),
    order_date       DATE            NOT NULL,            -- 订单日期
    delivery_date    DATE,                                -- 要求交期
    status           VARCHAR(32)     NOT NULL DEFAULT 'pending',
        -- pending(待发货) / partial(部分发货) / completed(已完成) / cancelled(已取消)
    total_weight     DECIMAL(12,3),                       -- 总重量(kg)
    total_amount     DECIMAL(14,2),                       -- 总金额(元,可空)
    remark           TEXT,                                -- 备注

    -- AI 识别扩展字段(Phase 0 对接)
    document_type    VARCHAR(32)     DEFAULT 'purchase_order',
        -- purchase_order / outsourced_order / plan_order
    price_type       VARCHAR(16)     DEFAULT 'taxed',     -- taxed/notaxed/direct
    source_type      VARCHAR(16)     DEFAULT 'manual',    -- ai/manual
    ai_raw_result    JSONB,                               -- AI 原始识别结果
    source_file      VARCHAR(512),                        -- 上传文件路径

    created_by       VARCHAR(64),
    created_at       TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at       TIMESTAMP       NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_orders_no ON orders(order_no);
CREATE INDEX idx_orders_customer ON orders(customer_id);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_date ON orders(order_date);

单号生成规则JK + YYYYMMDD + 3位序号
例如:JK20260524001,每天从 001 重新计数。

2.5 订单明细表 (order_items)
CREATE TABLE order_items (
    id               BIGSERIAL       PRIMARY KEY,
    order_id         BIGINT          NOT NULL
                     REFERENCES orders(id) ON DELETE CASCADE,

    -- 规格字段(自描述,不强制关联产品表)
    product_name     VARCHAR(200),                        -- 品名:紫铜卷料/紫铜板/铜排
    material_type    VARCHAR(16)     NOT NULL DEFAULT 'coil',
        -- coil(卷材) / sheet(张片) / profile(型材)
    spec             VARCHAR(200),                        -- 完整规格字符串
    hardness         VARCHAR(16),                         -- 状态:H/Y2/M

    -- 数量/重量
    quantity         DECIMAL(12,3)   NOT NULL,            -- 订购件数
    weight_kg        DECIMAL(12,3),                       -- 重量(kg)
    unit_price       DECIMAL(12,2),                       -- 单价(元/kg)
    amount           DECIMAL(14,2),                       -- 金额

    -- 发货跟踪(核心:欠量管理)
    ordered_weight   DECIMAL(12,3)   NOT NULL,            -- 订购重量(kg)
    delivered_weight DECIMAL(12,3)   DEFAULT 0,           -- 已发货重量(kg)
    pending_weight   DECIMAL(12,3)   GENERATED ALWAYS AS (ordered_weight - delivered_weight) STORED,  -- 欠量(自动计算)

    remark           TEXT,

    CONSTRAINT fk_order FOREIGN KEY (order_id) REFERENCES orders(id)
);

CREATE INDEX idx_items_order ON order_items(order_id);
CREATE INDEX idx_items_type ON order_items(material_type);

说明: - pending_weight 使用 PostgreSQL GENERATED ALWAYS AS ... STORED 计算列,由数据库自动维护,始终保持 ordered_weight - delivered_weight 的值,无需应用层手动更新 - 每行明细独立定义规格,不强制关联产品主数据,便于 AI 识别场景

2.6 入库单表 (inbound_orders)
CREATE TABLE inbound_orders (
    id            BIGSERIAL       PRIMARY KEY,
    inbound_no    VARCHAR(64)     UNIQUE NOT NULL,        -- 入库单号
    supplier_id   BIGINT          REFERENCES suppliers(id), -- 供应商
    inbound_date  DATE            NOT NULL,               -- 入库日期
    type          VARCHAR(32)     NOT NULL DEFAULT 'purchase',
        -- purchase(来料入库) / return(退货入库)
    status        VARCHAR(16)     NOT NULL DEFAULT 'draft',
        -- draft(草稿) / completed(已完成) / cancelled(已取消)
    remark        TEXT,
    created_by    VARCHAR(64),
    created_at    TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMP       NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_inbound_no ON inbound_orders(inbound_no);
CREATE INDEX idx_inbound_supplier ON inbound_orders(supplier_id);
CREATE INDEX idx_inbound_date ON inbound_orders(inbound_date);

单号生成规则RK + YYYYMMDD + 3位序号
例如:RK20260524001

2.7 入库明细表 (inbound_items)
CREATE TABLE inbound_items (
    id              BIGSERIAL       PRIMARY KEY,
    inbound_id      BIGINT          NOT NULL
                    REFERENCES inbound_orders(id) ON DELETE CASCADE,

    product_name    VARCHAR(200)    NOT NULL,              -- 品名
    spec            VARCHAR(200),                          -- 规格
    hardness        VARCHAR(16),                           -- 状态

    quantity        INTEGER         NOT NULL,              -- 件数
    weight_kg       DECIMAL(12,3)   NOT NULL,              -- 重量(kg)

    batch_no        VARCHAR(64),                           -- 批号
    internal_no     VARCHAR(64),                           -- 内部编号(二维码内容)
    location        VARCHAR(64),                           -- 仓位
    quality_status  VARCHAR(16)     NOT NULL DEFAULT 'qualified',
        -- qualified(正常) / pending(待检) / returned(退货)

    remark          TEXT,

    CONSTRAINT fk_inbound FOREIGN KEY (inbound_id) REFERENCES inbound_orders(id)
);

CREATE INDEX idx_inbound_items_inbound ON inbound_items(inbound_id);
CREATE INDEX idx_inbound_items_batch ON inbound_items(batch_no);

2.8 库存表 (inventory)
CREATE TABLE inventory (
    id                BIGSERIAL       PRIMARY KEY,

    product_name      VARCHAR(200)    NOT NULL,            -- 品名
    spec              VARCHAR(200),                        -- 规格
    hardness          VARCHAR(16),                         -- 状态

    quantity_available INTEGER       NOT NULL DEFAULT 0,   -- 可用件数
    weight_available  DECIMAL(12,3)  NOT NULL DEFAULT 0,   -- 可用重量(kg)

    batch_no          VARCHAR(64),                         -- 批号
    internal_no       VARCHAR(64),                         -- 内部编号
    location          VARCHAR(64),                         -- 仓位
    quality_status    VARCHAR(16)    NOT NULL DEFAULT 'qualified',
        -- qualified(正常) / pending(待检) / returned(退货) / defective(次品)

    supplier_id       BIGINT         REFERENCES suppliers(id),  -- 来源供应商
    inbound_date      DATE,                                -- 入库日期
    inbound_item_id   BIGINT,                              -- 关联入库明细(溯源)

    updated_at        TIMESTAMP      NOT NULL DEFAULT NOW()
);

-- 同品名+同规格+同批号+同仓位 → 合并为一条记录
CREATE UNIQUE INDEX idx_inventory_uniq ON inventory(product_name, spec, batch_no, location, quality_status);
CREATE INDEX idx_inventory_batch ON inventory(batch_no);
CREATE INDEX idx_inventory_location ON inventory(location);
CREATE INDEX idx_inventory_status ON inventory(quality_status);

核心设计原则: - 库存按批次+仓位做唯一约束,同一规格不同批号不同仓位各自独立 - quantity_availableweight_available 通过入库/出库/加工触发器同步更新 - 出库时支持按 FEFO(先到期先出)或指定批次

2.9 出库单表 (outbound_orders)
CREATE TABLE outbound_orders (
    id            BIGSERIAL       PRIMARY KEY,
    outbound_no   VARCHAR(64)     UNIQUE NOT NULL,        -- 出库单号
    order_id      BIGINT          REFERENCES orders(id),  -- 关联销售订单
    customer_id   BIGINT          REFERENCES customers(id), -- 客户
    outbound_date DATE            NOT NULL,               -- 出库日期
    status        VARCHAR(16)     NOT NULL DEFAULT 'draft',
        -- draft(草稿) / completed(已完成) / cancelled(已取消)
    remark        TEXT,
    created_by    VARCHAR(64),
    created_at    TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMP       NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_outbound_no ON outbound_orders(outbound_no);
CREATE INDEX idx_outbound_order ON outbound_orders(order_id);
CREATE INDEX idx_outbound_customer ON outbound_orders(customer_id);
CREATE INDEX idx_outbound_date ON outbound_orders(outbound_date);

单号生成规则CK + YYYYMMDD + 3位序号
例如:CK20260524001

2.10 出库明细表 (outbound_items)
CREATE TABLE outbound_items (
    id              BIGSERIAL       PRIMARY KEY,
    outbound_id     BIGINT          NOT NULL
                    REFERENCES outbound_orders(id) ON DELETE CASCADE,
    inventory_id    BIGINT          REFERENCES inventory(id),   -- 关联库存批次

    -- 冗余字段(方便查询,不依赖关联)
    product_name    VARCHAR(200)    NOT NULL,
    spec            VARCHAR(200),
    hardness        VARCHAR(16),

    quantity        INTEGER         NOT NULL,              -- 出库件数
    weight_kg       DECIMAL(12,3)   NOT NULL,              -- 出库重量(kg)
    batch_no        VARCHAR(64),

    remark          TEXT,

    CONSTRAINT fk_outbound FOREIGN KEY (outbound_id) REFERENCES outbound_orders(id)
);

CREATE INDEX idx_outbound_items_outbound ON outbound_items(outbound_id);
CREATE INDEX idx_outbound_items_inventory ON outbound_items(inventory_id);

2.11 加工单表 (processing_orders)
CREATE TABLE processing_orders (
    id               BIGSERIAL       PRIMARY KEY,
    process_no       VARCHAR(64)     UNIQUE NOT NULL,     -- 加工单号
    order_item_id    BIGINT          REFERENCES order_items(id), -- 关联订单明细

    process_type     VARCHAR(16)     NOT NULL,
        -- slitting(分切) / shearing(剪切)

    -- 母材信息
    parent_material_inventory_id BIGINT REFERENCES inventory(id),
    parent_spec      VARCHAR(200),                        -- 母材规格
    parent_weight    DECIMAL(12,3),                       -- 母材投入重量

    -- 产出信息
    child_spec       VARCHAR(200)   NOT NULL,              -- 子材规格
    output_weight    DECIMAL(12,3)  NOT NULL,              -- 产出重量

    -- 损耗/余料
    scrap_weight     DECIMAL(12,3)  DEFAULT 0,            -- 废料重量
    return_weight    DECIMAL(12,3)  DEFAULT 0,            -- 退库余料重量

    process_date     DATE           NOT NULL,              -- 加工日期
    status           VARCHAR(16)    NOT NULL DEFAULT 'pending',
        -- pending(待加工) / completed(已完成) / cancelled(已取消)

    remark           TEXT,
    created_by       VARCHAR(64),
    created_at       TIMESTAMP      NOT NULL DEFAULT NOW(),
    updated_at       TIMESTAMP      NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_processing_no ON processing_orders(process_no);
CREATE INDEX idx_processing_order ON processing_orders(order_item_id);
CREATE INDEX idx_processing_parent ON processing_orders(parent_material_inventory_id);

加工业务流

母材库存 → 创建加工单 → 分切/剪切 → 产出子材 → 子材入库存
                                    → 废料记录
                                    → 余料退库

2.12 二维码标签表 (qr_labels)
CREATE TABLE qr_labels (
    id                    BIGSERIAL       PRIMARY KEY,
    label_no              VARCHAR(64)     UNIQUE NOT NULL,     -- 标签编号
    batch_no              VARCHAR(64),                         -- 批号
    product_name          VARCHAR(200),                        -- 品名
    spec                  VARCHAR(200),                        -- 规格
    weight_kg             DECIMAL(12,3),                       -- 重量(kg)

    qr_content            TEXT            NOT NULL,             -- 二维码完整内容(JSON)
    status                VARCHAR(16)     NOT NULL DEFAULT 'unscanned',
        -- unscanned(未扫码) / inbound(已入库) / outbound(已出库)
    scanned_at            TIMESTAMP,                           -- 扫码时间
    source                VARCHAR(16),                         -- inbound/outbound
    related_transaction_id BIGINT,                             -- 关联交易ID

    created_at            TIMESTAMP       NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_qr_label_no ON qr_labels(label_no);
CREATE INDEX idx_qr_batch ON qr_labels(batch_no);
CREATE INDEX idx_qr_status ON qr_labels(status);

2.13 系统用户表 (users)
CREATE TABLE users (
    id            BIGSERIAL       PRIMARY KEY,
    username      VARCHAR(64)     UNIQUE NOT NULL,        -- 用户名
    password_hash VARCHAR(256)    NOT NULL,                -- bcrypt 哈希
    role          VARCHAR(32)     NOT NULL DEFAULT 'operator',
        -- admin(管理员) / supervisor(业务主管) / operator(操作员) / viewer(只读)
    display_name  VARCHAR(100)    NOT NULL,                -- 显示名称
    status        VARCHAR(16)     NOT NULL DEFAULT 'active',
    created_at    TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMP       NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_role ON users(role);

2.14 操作日志表 (operation_logs)
CREATE TABLE operation_logs (
    id             BIGSERIAL       PRIMARY KEY,
    user_id        BIGINT          REFERENCES users(id),
    username       VARCHAR(64),
    action         VARCHAR(32)     NOT NULL,               -- create/update/delete/login
    resource_type  VARCHAR(32)     NOT NULL,               -- order/inventory/customer/...
    resource_id    BIGINT,
    detail         TEXT,                                   -- JSON 变更内容
    ip_address     VARCHAR(45),
    created_at     TIMESTAMP       NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_logs_resource ON operation_logs(resource_type, resource_id);
CREATE INDEX idx_logs_user ON operation_logs(user_id);
CREATE INDEX idx_logs_time ON operation_logs(created_at);

3. API 设计

3.1 通用约定
基础路径: /api/v1
请求头:   Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json

统一响应格式:
{
  "code": 0,              // 0=成功, 非0=错误码
  "message": "ok",         // 提示信息
  "data": {},              // 返回数据(对象或数组)
  "pagination": {          // 分页时返回
    "page": 1,
    "page_size": 20,
    "total": 100
  }
}

错误响应:
{
  "code": 40001,
  "message": "参数错误: 客户名称不能为空"
}

常见错误码:
  - 40001~40099: 参数校验错误
  - 40100~40199: 认证/授权错误
  - 40300~40399: 业务逻辑错误(库存不足等)
  - 40400~40499: 资源不存在
  - 50000: 服务器内部错误

3.2 认证 API

POST /api/v1/auth/login

登录获取 JWT Token。

请求体

{
  "username": "admin",
  "password": "password123"
}

响应

{
  "code": 0,
  "data": {
    "access_token": "eyJhbGciOiJIUzI1NiIs...",
    "expires_in": 86400,
    "user": {
      "id": 1,
      "username": "admin",
      "display_name": "管理员",
      "role": "admin"
    }
  }
}

GET /api/v1/auth/me

获取当前用户信息(需要 Authorization Header)。

PUT /api/v1/auth/password

修改密码。

3.3 客户管理 API

GET /api/v1/customers

客户列表(分页+搜索+筛选)。

查询参数?page=1&page_size=20&keyword=搜索词&status=active

响应

{
  "code": 0,
  "data": [
    {
      "id": 1,
      "code": "C001",
      "name": "东莞越洋达电子科技有限公司",
      "contact": "张先生",
      "phone": "13800138000",
      "address": "东莞市长安镇...",
      "credit_terms": "月结30天",
      "status": "active"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 50
  }
}

POST /api/v1/customers

新建客户。

请求体

{
  "code": "C002",
  "name": "东莞鸿阳精密电子有限公司",
  "contact": "李先生",
  "phone": "13900139000",
  "address": "东莞市虎门镇...",
  "credit_terms": "月结60天"
}

GET /api/v1/customers/:id

客户详情。

PUT /api/v1/customers/:id

更新客户。

DELETE /api/v1/customers/:id

删除客户(软删除:修改 status 为 disabled)。

3.4 供应商管理 API

同客户管理模式:

方法 路径 说明
GET /api/v1/suppliers 供应商列表
POST /api/v1/suppliers 新建供应商
GET /api/v1/suppliers/:id 供应商详情
PUT /api/v1/suppliers/:id 更新供应商
DELETE /api/v1/suppliers/:id 删除供应商

3.5 订单管理 API

POST /api/v1/orders

创建订单(含明细)。

请求体

{
  "customer_id": 1,
  "customer_po_no": "PO20260524001",
  "order_date": "2026-05-24",
  "delivery_date": "2026-06-15",
  "items": [
    {
      "product_name": "紫铜卷料",
      "material_type": "coil",
      "spec": "T2.9×W175mm",
      "hardness": null,
      "quantity": 5,
      "weight_kg": 4410,
      "unit_price": 6.00,
      "amount": 26460.00,
      "ordered_weight": 4410
    }
  ]
}

响应

{
  "code": 0,
  "data": {
    "id": 42,
    "order_no": "JK20260524001",
    "status": "pending",
    "total_weight": 4410.000,
    "total_amount": 26460.00
  }
}

GET /api/v1/orders

订单列表(分页+筛选)。

查询参数?page=1&status=pending&customer_id=1&date_from=2026-05-01&date_to=2026-05-31

GET /api/v1/orders/:id

订单详情(含明细)。

响应

{
  "code": 0,
  "data": {
    "id": 42,
    "order_no": "JK20260524001",
    "customer": { "id": 1, "name": "东莞越洋达" },
    "status": "partial",
    "items": [
      {
        "id": 1,
        "product_name": "紫铜卷料",
        "spec": "T2.9×W175mm",
        "ordered_weight": 4410.000,
        "delivered_weight": 2000.000,
        "pending_weight": 2410.000,
        "quantity": 5,
        "weight_kg": 4410.000
      }
    ],
    "summary": {
      "total_weight": 4410.000,
      "total_delivered": 2000.000,
      "total_pending": 2410.000
    }
  }
}

PUT /api/v1/orders/:id

更新订单(仅草稿/待发货状态可修改)。

DELETE /api/v1/orders/:id

删除订单(仅待发货状态可删除)。

3.6 入库 API

POST /api/v1/inbound

创建入库单(含明细,自动增加库存)。

请求体

{
  "supplier_id": 1,
  "inbound_date": "2026-05-24",
  "type": "purchase",
  "items": [
    {
      "product_name": "紫铜卷料",
      "spec": "T2.9×W175mm",
      "hardness": null,
      "quantity": 10,
      "weight_kg": 8820,
      "batch_no": "202605-01",
      "location": "A-01-01",
      "quality_status": "qualified"
    }
  ]
}

响应

{
  "code": 0,
  "data": {
    "id": 1,
    "inbound_no": "RK20260524001",
    "status": "completed",
    "items_count": 1
  }
}

GET /api/v1/inbound

入库记录列表。

GET /api/v1/inbound/:id

入库单详情(含明细)。

3.7 出库 API

POST /api/v1/outbound

创建出库单(含库存批次选择,自动扣减库存)。

请求体

{
  "order_id": 42,
  "customer_id": 1,
  "outbound_date": "2026-05-24",
  "items": [
    {
      "inventory_id": 5,
      "product_name": "紫铜卷料",
      "spec": "T2.9×W175mm",
      "quantity": 2,
      "weight_kg": 1764,
      "batch_no": "202605-01"
    }
  ]
}

响应

{
  "code": 0,
  "data": {
    "id": 1,
    "outbound_no": "CK20260524001",
    "status": "completed"
  }
}

GET /api/v1/outbound

出库记录列表。

GET /api/v1/outbound/:id

出库单详情(含明细)。

3.8 库存 API

GET /api/v1/inventory

库存列表(多维筛选)。

查询参数

?product_name=紫铜&spec=T2.9&batch_no=202605&location=A-01&quality_status=qualified&page=1

响应

{
  "code": 0,
  "data": [
    {
      "id": 5,
      "product_name": "紫铜卷料",
      "spec": "T2.9×W175mm",
      "hardness": null,
      "quantity_available": 8,
      "weight_available": 7056.000,
      "batch_no": "202605-01",
      "location": "A-01-01",
      "quality_status": "qualified",
      "inbound_date": "2026-05-24"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 15
  }
}

POST /api/v1/inventory/check

库存盘点。

请求体

{
  "inventory_id": 5,
  "actual_quantity": 7,
  "actual_weight": 6174.000,
  "remark": "盘点调整:发现1件次品转出"
}

GET /api/v1/inventory/low-stock

低库存预警列表(低于安全阈值,默认 100kg)。

3.9 加工管理 API

POST /api/v1/processing

创建加工单。

请求体

{
  "order_item_id": 1,
  "process_type": "slitting",
  "parent_material_inventory_id": 5,
  "parent_spec": "T2.9×W175mm",
  "parent_weight": 1764.000,
  "child_spec": "T2.9×W135mm",
  "output_weight": 1700.000,
  "scrap_weight": 40.000,
  "return_weight": 24.000,
  "process_date": "2026-05-24"
}

GET /api/v1/processing

加工单列表。

GET /api/v1/processing/:id

加工单详情。

3.10 二维码 API

POST /api/v1/qr/generate

生成二维码标签。

请求体

{
  "batch_no": "202605-01",
  "product_name": "紫铜卷料",
  "spec": "T2.9×W175mm",
  "weight_kg": 882.000,
  "count": 10
}

响应

{
  "code": 0,
  "data": {
    "labels": [
      {
        "label_no": "QR20260524001",
        "qr_content": "{\"label_no\":\"QR20260524001\",\"product_name\":\"紫铜卷料\",\"spec\":\"T2.9×W175mm\",\"weight_kg\":882.000,\"batch_no\":\"202605-01\"}",
        "qr_image": "data:image/png;base64,..."
      }
    ]
  }
}

POST /api/v1/qr/scan

扫码处理(入库或出库)。

请求体(入库场景):

{
  "qr_content": "{...完整二维码JSON...}",
  "action": "inbound",
  "inbound_id": 1
}

请求体(出库场景):

{
  "qr_content": "{...完整二维码JSON...}",
  "action": "outbound",
  "outbound_id": 1
}

3.11 AI 识别 API(复用 Phase 0)

POST /api/v1/ocr/recognize

上传图片进行 AI 识别。

请求multipart/form-data,字段 file(图片文件)

响应

{
  "code": 0,
  "data": {
    "customer_name": "东莞越洋达电子科技有限公司",
    "po_number": "1020PM2605190004",
    "order_date": "2026-05-19",
    "items": [
      {
        "spec_text": "T2.9×W175mm, T2紫铜",
        "material": "T2",
        "thickness": 2.9,
        "width": 175,
        "length": null,
        "hardness": null,
        "product_name": "紫铜卷料",
        "material_type": "coil",
        "weight_kg": 4410.000
      }
    ]
  }
}

3.12 报表 API

GET /api/v1/reports/inventory

库存表导出。

查询参数?format=json(默认)或 ?format=xlsx

响应(JSON 格式)

{
  "code": 0,
  "data": {
    "headers": ["品名", "规格", "件数", "重量(kg)", "批号", "仓位", "品质状态"],
    "rows": [
      ["紫铜卷料", "T2.9×W175mm", 8, 7056.000, "202605-01", "A-01-01", "正常"]
    ]
  }
}

响应(xlsx 格式):直接返回 Excel 文件下载。

GET /api/v1/reports/order-delivery

订单发货表导出。

查询参数?order_no=JK20260524001&customer_id=1&format=xlsx

响应列:订单号 / 客户 / 品名 / 规格 / 订购重量 / 已发重量 / 欠量 / 发货进度


4. 前端页面规划

4.1 页面总览
页面 路由 说明 所属阶段
登录页 /login 用户名/密码登录 Phase 1
仪表盘 /dashboard 概览数据:今日入库/出库/库存预警/待办订单 Phase 1
订单管理 /orders 订单列表 + 新建 + 详情 Phase 1
入库管理 /inbound 入库单列表 + 新建(含二维码扫码) Phase 1
出库管理 /outbound 出库单列表 + 新建(含库存选择) Phase 2
库存台账 /inventory 多维筛选 + 导出 Excel Phase 1
加工管理 /processing 加工单列表 + 新建加工单 Phase 2
二维码管理 /qr 标签生成 + 扫码记录查询 Phase 3
客户管理 /customers 客户 CRUD Phase 1
供应商管理 /suppliers 供应商 CRUD Phase 1
报表 /reports 库存表 / 订单发货表 Phase 1
系统设置 /settings 用户管理 Phase 1

4.2 仪表盘(Dashboard.vue)

卡片布局: - 今日入库(件数/重量) - 今日出库(件数/重量) - 待发订单数(含欠量汇总) - 低库存预警(低于 100kg 的物料列表)

快捷入口: - 新建入库单 - 新建出库单 - 新建订单

4.3 订单管理页

OrderList.vue: - 表格列:订单号 / 客户 / 日期 / 总重量 / 状态(标签色) / 操作 - 筛选栏:关键字搜索、状态下拉、日期范围 - 行操作:查看详情、创建出库单(部分发货时)

OrderCreate.vue: - 顶部:客户选择(搜索下拉)、客户PO号、订单日期、交期 - 明细表格:每行可添加/删除,含品名/规格/件数/重量/单价/金额 - 底部汇总:总件数、总重量、总金额 - 两阶段流程(AI识别场景): 1. 上传图片 → AI 识别填充 2. 人工确认/修改 → 提交保存

OrderDetail.vue: - 订单信息(只读展示) - 明细表格(含发货进度条:已发/欠量) - 出库记录关联列表 - 操作按钮:创建出库单、取消订单

4.4 入库管理页

InboundList.vue: - 表格列:入库单号 / 日期 / 供应商 / 类型 / 状态 / 操作 - 筛选:日期范围、供应商、类型

InboundCreate.vue: - 供应商选择 - 入库日期 - 明细表格(可添加多行): - 品名 / 规格 / 件数 / 重量(kg) / 批号 / 仓位 / 品质状态 - 每行可附带二维码扫描:扫码后自动填充品名/规格/批号/重量 - 提交后自动更新库存

4.5 出库管理页

OutboundList.vue: - 表格列:出库单号 / 日期 / 客户 / 关联订单 / 状态 / 操作

OutboundCreate.vue: - 选择关联订单(或直接出库) - 库存选择器:按品名/规格/批号搜索可出库的库存批次 - 显示每个批次的可用件数和重量 - 选择后自动扣减当前出库数量 - 明细表格(从库存选择添加): - 品名 / 规格 / 批号 / 仓位 / 可出库重量 / 本次出库重量 - 提交 → 扣减库存 + 更新订单发货量

4.6 库存台账页

InventoryList.vue: - 筛选栏:品名 / 规格 / 批号 / 仓位 / 品质状态 - 表格列:品名 / 规格 / 件数 / 重量(kg) / 批号 / 仓位 / 品质状态 / 入库日期 - 操作:导出 Excel、打印 - 行详情:点击可查看该批次出入库流水

4.7 加工管理页

ProcessingList.vue: - 表格列:加工单号 / 母材 / 子材 / 类型 / 日期 / 状态 / 操作

ProcessingCreate.vue: - 关联订单明细 - 选择母材库存批次 - 填写产出规格/重量/废料/余料 - 提交 → 母材库存扣减 + 子材入库 + 余料退库

4.8 二维码管理页

QRGenerate.vue: - 选择批号/品名/规格/重量 - 批量生成标签(指定数量) - 预览/打印标签

QRScanRecord.vue: - 查询历史扫码记录 - 按时间/状态/来源筛选

4.9 客户/供应商管理页

CustomerList.vue / SupplierList.vue: - 搜索+列表+CRUD 弹窗 - 简单表单:名称/联系人/电话/地址等


5. 二维码集成方案

5.1 标签数据结构定义

二维码内容为 JSON 格式,压缩到单行,长度控制在 500 字符以内:

{
  "v": "1",
  "l": "QR20260524001",
  "p": "紫铜卷料",
  "s": "T2.9×W175mm",
  "w": 882.000,
  "b": "202605-01",
  "u": "kg",
  "d": "2026-05-24"
}
字段 含义 示例
v 数据版本 1
l 标签编号 QR20260524001
p 品名 紫铜卷料
s 规格 T2.9×W175mm
w 重量 882.000
b 批号 202605-01
u 单位 kg
d 日期 2026-05-24

5.2 扫码入库流程
① 来料到达 → 仓库员在入库页选择「扫码入库」
② 扫描每个物料上的二维码标签
    → 系统解析 JSON,自动填充:品名/规格/重量/批号
    → 仓库员补充:件数、仓位、品质状态
③ 全部扫描完成 → 确认提交
    → 系统创建入库单 + 每条二维码标记为「已入库」
    → 库存表自动增加

异常处理: - 无标签物料:手动录入所有字段 - 二维码损坏:手动输入标签编号,系统回查 - 重复扫码:提示「该标签已入库」

5.3 扫码出库流程
① 拣货 → 仓库员在出库页选择「扫码出库」
② 扫描每个待出库物料上的二维码
    → 系统解析 JSON,匹配库存批次
    → 显示确认信息:品名/规格/批号/仓位/可用重量
    → 仓库员输入出库重量(支持部分出库)
③ 全部扫描完成 → 确认提交
    → 库存扣减 + 二维码标记「已出库」
    → 如关联订单,更新订单发货进度

异常处理: - 库存不足:提示可用量不足,不能超量出库 - 品质不符:仅允许出库品质为「正常」的批次 - 已出库标签:提示「该标签已出库」

5.4 与 BarTender 设备的对接方式

今宽现场使用 BarTender 标签打印机。

推荐方案(服务器端生成 + 打印): 1. 系统生成二维码标签 → 输出为 PNG/PDF 2. BarTender 支持 PDF 打印模板,通过命令行或 API 调用 3. 或系统提供批量导出 → 在 BarTender 中排版打印

替代方案(打印服务): - 在后端集成 python-barcode + qrcode 库生成二维码图片 - 前端调用 /api/v1/qr/generate → 获取 base64 图片 → 直接使用浏览器打印 - 标签模板为 A4 不干胶(每页 10 个标签,2×5 布局)

数据结构同步: - BarTender 生成的二维码内容由系统 API 提供 - 今宽操作员通过系统界面生成标签编号 → 系统返回二维码内容 - 将二维码内容传给 BarTender(通过 TXT/CSV 中间文件)→ 打印贴标


6. AI 识别集成方案

6.1 复用 Phase 0 现有代码

已有模块可直接复用:

模块 文件 用处
腾讯云 OCR tencent_ocr.py 图片文字提取(稳定可重现)
规格解析器 spec_parser.py 规格字符串 → 结构化字段
客户模板匹配 template_matcher.py 识别客户信息
识别编排 recognizer.py 整合 OCR + 解析流程
Flask 主入口 app.py API 路由基础

6.2 系统集成调用流程
用户上传采购单图片
       │
       ▼
POST /api/v1/ocr/recognize
       │
       ├─ ① 图片预处理 (preprocessor.py)
       │    → 转灰度 / 降噪 / 增强对比度
       │
       ├─ ② 腾讯云 OCR 全文提取 (tencent_ocr.py)
       │    → 返回结构化文本
       │
       ├─ ③ 客户识别 (template_matcher.py)
       │    → 匹配客户模板 → 提取 PO号/日期/地址
       │
       ├─ ④ 规格解析 (spec_parser.py)
       │    → 逐行解析: 材质/厚度/宽度/长度/硬度/品名
       │    → 推断订单类别: coil/sheet/profile
       │
       └─ ⑤ 返回前端确认页
            → 用户逐项检查/修改
            → 确认提交 → 创建订单

6.3 字段提取与映射规则
材质映射:
  "C1100" / "T2" / "紫铜" / "红铜" → C1100
  "C1020" / "无氧铜"             → C1020
  "H62" / "黄铜"                → H62
  (默认 C1100 兜底)

类别推断:
  检测到 2 个维度(厚度×宽度)              → coil(卷材)
  检测到 3 个维度(厚度×宽度×长度)          → sheet(张片) 或 profile(型材)
  含 "铜排"/"型材" 关键词                  → profile
  含 "铜带"/"卷料" 关键词                  → coil

硬度提取:
  从规格文本中匹配 H/Y2/M 状态标记
  无标记 → null(部分铜材无硬度)

6.4 识别确认到订单录入的完整数据流
用户上传图片
    │
    ▼
[后端] ocr/recognize 接口
    │
    ├─ 腾讯云 OCR → 原文文本
    ├─ 本地解析 → 结构化字段
    └─ 返回 JSON 给前端
    │
    ▼
[前端] 确认页面 (confirm.vue)
    │
    ├─ 用户逐行修改(所有字段可改)
    ├─ 前端即时校验(规格完整性/数量合理性)
    └─ 点击「确认保存」
    │
    ▼
[前端] POST /api/v1/orders (含 AI 识别结果)
    │
    ▼
[后端] 创建订单
    │
    ├─ 生成订单号
    ├─ 写入 orders + order_items
    ├─ 存储 AI 原始结果 (ai_raw_result)
    └─ 返回订单 ID
    │
    ▼
[前端] 跳转到订单详情页

7. 开发规范

7.1 项目目录结构
~/Desktop/安徽今宽/10-开发/
│
├── backend/                         # Flask API 后端
│   ├── run.py                       # 启动入口
│   ├── requirements.txt             # Python 依赖
│   ├── .env.example                 # 环境变量模板
│   ├── app/
│   │   ├── __init__.py              # App 工厂 (create_app)
│   │   ├── config.py                # 配置管理
│   │   ├── errors.py                # 统一错误处理
│   │   │
│   │   ├── models/                  # SQLAlchemy 数据模型
│   │   │   ├── __init__.py
│   │   │   ├── customer.py
│   │   │   ├── supplier.py
│   │   │   ├── order.py
│   │   │   ├── order_item.py
│   │   │   ├── inventory.py
│   │   │   ├── inbound.py
│   │   │   ├── outbound.py
│   │   │   ├── processing.py
│   │   │   ├── qr_label.py
│   │   │   └── user.py
│   │   │
│   │   ├── api/                     # RESTful API 路由 (Blueprint)
│   │   │   ├── __init__.py
│   │   │   ├── auth.py
│   │   │   ├── customers.py
│   │   │   ├── suppliers.py
│   │   │   ├── orders.py
│   │   │   ├── inbound.py
│   │   │   ├── outbound.py
│   │   │   ├── inventory.py
│   │   │   ├── processing.py
│   │   │   ├── qr.py
│   │   │   ├── ocr.py              # AI 识别接口(对接 Phase 0)
│   │   │   └── reports.py
│   │   │
│   │   ├── services/                # 业务逻辑层
│   │   │   ├── __init__.py
│   │   │   ├── order_service.py      # 订单创建/状态流转
│   │   │   ├── inventory_service.py  # 库存增减
│   │   │   ├── inbound_service.py    # 入库 + 库存新增
│   │   │   ├── outbound_service.py   # 出库 + 库存扣减 + 订单更新
│   │   │   ├── processing_service.py # 加工 + 库存转换
│   │   │   └── qr_service.py         # 二维码生成/扫码处理
│   │   │
│   │   ├── utils/                   # 工具函数
│   │   │   ├── __init__.py
│   │   │   ├── auth.py              # JWT / 权限装饰器
│   │   │   ├── validators.py        # 参数校验
│   │   │   ├── helpers.py           # 通用工具
│   │   │   └── docno.py             # 单号生成器
│   │   │
│   │   └── ext/                     # Phase 0 已有代码(不改造,直接引用)
│   │       ├── tencent_ocr.py
│   │       ├── spec_parser.py
│   │       ├── template_matcher.py
│   │       ├── template_learner.py
│   │       ├── recognizer.py
│   │       ├── preprocessor.py
│   │       └── rate_limiter.py
│   │
│   ├── migrations/                  # Flask-Migrate 迁移
│   │
│   └── tests/                       # 单元测试
│       ├── __init__.py
│       ├── test_orders.py
│       ├── test_inventory.py
│       └── test_inbound_outbound.py
│
├── frontend/                        # Vue3 前端
│   ├── package.json
│   ├── vite.config.js
│   ├── index.html
│   ├── src/
│   │   ├── main.js
│   │   ├── App.vue
│   │   ├── router/
│   │   │   └── index.js
│   │   ├── stores/                  # Pinia
│   │   │   ├── auth.js
│   │   │   ├── order.js
│   │   │   └── inventory.js
│   │   ├── api/                     # Axios 封装
│   │   │   ├── index.js
│   │   │   ├── auth.js
│   │   │   ├── orders.js
│   │   │   ├── inbound.js
│   │   │   ├── outbound.js
│   │   │   ├── inventory.js
│   │   │   └── reports.js
│   │   ├── views/
│   │   │   ├── Login.vue
│   │   │   ├── Dashboard.vue
│   │   │   ├── order/
│   │   │   │   ├── OrderList.vue
│   │   │   │   ├── OrderCreate.vue
│   │   │   │   └── OrderDetail.vue
│   │   │   ├── inbound/
│   │   │   │   ├── InboundList.vue
│   │   │   │   └── InboundCreate.vue
│   │   │   ├── outbound/
│   │   │   │   ├── OutboundList.vue
│   │   │   │   └── OutboundCreate.vue
│   │   │   ├── inventory/
│   │   │   │   └── InventoryList.vue
│   │   │   ├── customer/
│   │   │   │   └── CustomerList.vue
│   │   │   ├── supplier/
│   │   │   │   └── SupplierList.vue
│   │   │   ├── processing/
│   │   │   │   ├── ProcessingList.vue
│   │   │   │   └── ProcessingCreate.vue
│   │   │   ├── qr/
│   │   │   │   ├── QRGenerate.vue
│   │   │   │   └── QRScanRecord.vue
│   │   │   └── ReportView.vue
│   │   ├── components/             # 通用组件
│   │   │   ├── Navbar.vue
│   │   │   ├── Sidebar.vue
│   │   │   ├── Table.vue
│   │   │   ├── SearchForm.vue
│   │   │   ├── CustomerSelect.vue
│   │   │   ├── InventoryPicker.vue  # 库存批次选择组件(出库时使用)
│   │   │   └── QRScanner.vue        # 扫码输入组件
│   │   └── utils/
│   │       ├── formatters.js
│   │       └── validators.js
│   └── public/
│
├── scripts/
│   ├── init_db.py                  # 数据库表初始化
│   ├── seed_data.py                # 测试数据填充
│   └── deploy.sh                   # 一键部署
│
├── docker-compose.yml              # 本地开发环境
└── nginx/
    └── jinkuan.conf                # Nginx 配置

7.2 代码规范

Python (Flask)
# ─── 命名规范 ───
# 类名: PascalCase
class OrderService:
    pass

# 函数/方法: snake_case
def create_outbound_order(data):
    pass

# 变量: snake_case
order_no = "JK20260524001"

# 常量: UPPER_SNAKE_CASE
MAX_FILE_SIZE = 10 * 1024 * 1024

# ─── API 视图函数 ───
from flask import Blueprint, request, jsonify
from app.decorators import jwt_required, role_required

bp = Blueprint('orders', __name__, url_prefix='/api/v1/orders')

@bp.route('', methods=['GET'])
@jwt_required()
def list_orders():
    """
    订单列表
    ---
    GET /api/v1/orders
    查询参数: page, page_size, status, customer_id
    """
    # 1. 参数解析
    page = request.args.get('page', 1, type=int)
    page_size = request.args.get('page_size', 20, type=int)

    # 2. 业务逻辑调用 service
    result = OrderService.list_orders(page=page, page_size=page_size)

    # 3. 统一响应
    return jsonify(code=0, data=result['data'], pagination=result['pagination'])

JavaScript / Vue
// ─── 命名规范 ───
// 组件名: PascalCase
// 变量/函数: camelCase
// 常量: UPPER_SNAKE_CASE
const API_BASE = '/api/v1'

// ─── Vue 组件结构 ───
<script setup>
import { ref, onMounted } from 'vue'

const loading = ref(false)
const list = ref([])

onMounted(async () => {
  loading.value = true
  try {
    const res = await api.getOrderList()
    list.value = res.data
  } finally {
    loading.value = false
  }
})
</script>

7.3 注释规范
# ─── 模块级 docstring ───
"""入库业务逻辑

提供入库单创建、库存新增等业务操作。
"""

# ─── 函数 docstring ───
def create_inbound(data: dict) -> dict:
    """
    创建入库单并更新库存。

    Args:
        data: 入库单数据,包含供应商、明细行等

    Returns:
        创建的入库单对象(含单号、状态)

    Raises:
        ValidationError: 数据校验失败
        BusinessError: 业务逻辑异常(如库存操作失败)
    """
    pass

# ─── 复杂逻辑注释 ───
# 注意:出库时需要同时操作 3 张表
# 1. outbound_items 新增记录
# 2. inventory 扣减可用量
# 3. order_items 更新已发货量
# 全部在同一个事务中执行

7.4 错误处理规范
# ─── 自定义异常类 ───
class AppError(Exception):
    """应用基础异常"""
    def __init__(self, code: int, message: str, status_code=400):
        self.code = code
        self.message = message
        self.status_code = status_code

class ValidationError(AppError):
    """参数校验错误"""
    pass

class BusinessError(AppError):
    """业务逻辑错误(如库存不足)"""
    pass

class NotFoundError(AppError):
    """资源不存在"""
    def __init__(self, resource: str, resource_id):
        super().__init__(40401, f"{resource} (id={resource_id}) 不存在", 404)

# ─── 统一错误处理 ───
@app.errorhandler(AppError)
def handle_app_error(error):
    return jsonify(code=error.code, message=error.message), error.status_code

@app.errorhandler(404)
def handle_404(error):
    return jsonify(code=40400, message="接口不存在"), 404

@app.errorhandler(500)
def handle_500(error):
    app.logger.exception("服务器内部错误")
    return jsonify(code=50000, message="服务器内部错误"), 500

7.5 日志规范
import logging

# ─── 日志配置 ───
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
    handlers=[
        logging.StreamHandler(),                          # 控制台输出
        logging.FileHandler('/var/log/jinkuan/app.log'),  # 文件日志
    ]
)

logger = logging.getLogger('jinkuan')

# ─── 日志级别使用规范 ───
logger.info("用户 %s 创建了订单 %s", username, order_no)  # 正常业务操作
logger.warning("库存不足: 品名=%s, 可用=%s, 需求=%s", product, available, required)
logger.error("出库操作失败: %s", str(error))               # 业务错误
logger.exception("订单创建异常")                            # 未预期的异常(自动包含 traceback)

# ─── 操作日志(记录到数据库) ───
def log_operation(user_id, username, action, resource_type, resource_id, detail=None):
    """将关键操作记录到 operation_logs 表"""
    log = OperationLog(
        user_id=user_id,
        username=username,
        action=action,
        resource_type=resource_type,
        resource_id=resource_id,
        detail=json.dumps(detail, ensure_ascii=False) if detail else None,
        ip_address=request.remote_addr,
    )
    db.session.add(log)
    db.session.commit()

7.6 事务管理
from app import db

class InventoryService:
    @staticmethod
    @db.transaction()  # 事务装饰器,自动 commit/rollback
    def inbound(items):
        """入库操作:同时操作 inbound_items 和 inventory"""
        for item in items:
            # 1. 写入入库明细
            inbound_item = InboundItem(**item)
            db.session.add(inbound_item)

            # 2. 更新或创建库存(UPSERT)
            inv = Inventory.query.filter_by(
                product_name=item['product_name'],
                spec=item['spec'],
                batch_no=item['batch_no'],
                location=item['location'],
                quality_status=item.get('quality_status', 'qualified')
            ).first()

            if inv:
                inv.quantity_available += item['quantity']
                inv.weight_available += item['weight_kg']
            else:
                inv = Inventory(
                    product_name=item['product_name'],
                    spec=item['spec'],
                    batch_no=item['batch_no'],
                    location=item['location'],
                    quantity_available=item['quantity'],
                    weight_available=item['weight_kg'],
                    quality_status=item.get('quality_status', 'qualified'),
                )
                db.session.add(inv)

        # 事务装饰器自动 commit

7.7 库存扣减核心逻辑
class InventoryService:
    @staticmethod
    @db.transaction()
    def consume(inventory_id: int, quantity: int, weight_kg: float) -> bool:
        """
        扣减库存(带乐观锁,防止并发超卖)

        Args:
            inventory_id: 库存批次 ID
            quantity: 扣减件数
            weight_kg: 扣减重量(kg)

        Returns:
            True=扣减成功, False=库存不足

        Raises:
            BusinessError: 库存不足时
        """
        # 使用 SELECT ... FOR UPDATE 做悲观锁
        inv = Inventory.query.with_for_update().get(inventory_id)
        if not inv:
            raise NotFoundError('库存批次', inventory_id)

        if inv.quantity_available < quantity or inv.weight_available < weight_kg:
            raise BusinessError(
                40301,
                f"库存不足: 可用件数={inv.quantity_available}, 需={quantity}; "
                f"可用重量={inv.weight_available}kg, 需={weight_kg}kg"
            )

        inv.quantity_available -= quantity
        inv.weight_available -= weight_kg
        return True

7.8 订单状态自动更新
class OrderService:
    @staticmethod
    def update_order_delivery_status(order_id: int):
        """
        更新订单发货状态:
        - 所有明细 delivered_weight >= ordered_weight → completed
        - 部分明细 delivered_weight > 0 → partial
        - 全部 delivered_weight = 0 → pending(无变化)
        """
        order = Order.query.get(order_id)
        if not order:
            return

        items = OrderItem.query.filter_by(order_id=order_id).all()
        if not items:
            return

        all_completed = all(
            item.delivered_weight >= item.ordered_weight
            for item in items
        )
        any_delivered = any(item.delivered_weight > 0 for item in items)

        if all_completed:
            order.status = 'completed'
        elif any_delivered:
            order.status = 'partial'
        # else: 保持 pending

7.9 测试策略

测试分级
级别 范围 覆盖率目标 框架
单元测试 Service 层业务逻辑、工具函数、单号生成 ≥ 80% pytest
集成测试 API 接口(请求→响应)、数据库操作 ≥ 60% pytest + Flask test client
E2E 测试 核心流程:订单创建→入库→出库→库存变更 核心流程 100% 手工 + 未来 Playwright

核心测试场景(必须覆盖)及验收标准映射
# 测试场景 覆盖内容 对应需求方案验收项
1 订单流程 创建订单 → 订单明细含 pending_weight 自动计算 → 状态流转(pending→partial→completed) A1(订单录入)、A2(订单查询/编辑)
2 入库流程 创建入库单 → 库存自动增加 → 重复入库同一批次(幂等性) A3(入库登记)、A4(库存台账)
3 出库流程 选择库存批次出库 → 库存扣减(并发安全) → 订单发货量更新 → 库存不足时拒绝 A4(库存台账)、A5(订单发货表)、B3(出库联动)
4 分切加工 母材库存扣减 → 子材/边丝/余料入库 → 出材率计算 B1(分切加工单)、B2(材率计算与预警)
5 送货单/质保书 出库后一键生成送货单 PDF、质保书自动匹配模板含电子公章 B5(送货单PDF)、B6(质保书PDF)
6 批次追溯 从原材料批次到成品批次到客户,全程可追溯 B7(批次追溯)、C4(全链路追溯)
7 二维码流程 批量生成标签 → 扫码入库 → 扫码出库 → 状态流转 C1(标签生成)、C2(扫码入库)、C3(扫码出库)
8 库存查询 多维筛选(品名/规格/批号/仓位/品质状态)正确性 A4(库存台账)
9 单号生成 同一天同前缀单号不重复(并发测试) A1(订单录入-单号规范)
10 权限控制 未登录拒绝访问 API、不同角色权限隔离 A8(用户权限)
11 经营报表 仪表盘数据准确性、销售/库存/利润分析正确性 D1-D4(经营报表)

测试数据

运行命令
# 单元测试 + 覆盖率
pytest --cov=app --cov-report=term-missing tests/

# 仅单元测试(快速)
pytest -m unit tests/

# 集成测试
pytest -m integration tests/

8. 附录:DDL 完整脚本

8.1 完整建表脚本
-- =============================================
-- 今宽进销存系统 - 完整建表 DDL
-- 数据库: PostgreSQL 16
-- =============================================

-- 启用 UUID 扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- 8.1 客户表
CREATE TABLE customers (
    id            BIGSERIAL       PRIMARY KEY,
    code          VARCHAR(32)     UNIQUE NOT NULL,
    name          VARCHAR(200)    NOT NULL,
    contact       VARCHAR(100),
    phone         VARCHAR(32),
    address       TEXT,
    credit_terms  VARCHAR(64),
    remark        TEXT,
    status        VARCHAR(16)     NOT NULL DEFAULT 'active',
    created_at    TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMP       NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_customers_code ON customers(code);
CREATE INDEX idx_customers_status ON customers(status);

-- 8.2 供应商表
CREATE TABLE suppliers (
    id             BIGSERIAL       PRIMARY KEY,
    code           VARCHAR(32)     UNIQUE NOT NULL,
    name           VARCHAR(200)    NOT NULL,
    contact        VARCHAR(100),
    phone          VARCHAR(32),
    material_scope VARCHAR(200),
    remark         TEXT,
    status         VARCHAR(16)     NOT NULL DEFAULT 'active',
    created_at     TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at     TIMESTAMP       NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_suppliers_code ON suppliers(code);
CREATE INDEX idx_suppliers_status ON suppliers(status);

-- 8.3 订单表
CREATE TABLE orders (
    id               BIGSERIAL       PRIMARY KEY,
    order_no         VARCHAR(64)     UNIQUE NOT NULL,
    customer_po_no   VARCHAR(128),
    customer_id      BIGINT          NOT NULL REFERENCES customers(id),
    order_date       DATE            NOT NULL,
    delivery_date    DATE,
    status           VARCHAR(32)     NOT NULL DEFAULT 'pending',
    total_weight     DECIMAL(12,3),
    total_amount     DECIMAL(14,2),
    remark           TEXT,
    document_type    VARCHAR(32)     DEFAULT 'purchase_order',
    price_type       VARCHAR(16)     DEFAULT 'taxed',
    source_type      VARCHAR(16)     DEFAULT 'manual',
    ai_raw_result    JSONB,
    source_file      VARCHAR(512),
    created_by       VARCHAR(64),
    created_at       TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at       TIMESTAMP       NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_orders_no ON orders(order_no);
CREATE INDEX idx_orders_customer ON orders(customer_id);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_date ON orders(order_date);

-- 8.4 订单明细表
CREATE TABLE order_items (
    id               BIGSERIAL       PRIMARY KEY,
    order_id         BIGINT          NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
    product_name     VARCHAR(200),
    material_type    VARCHAR(16)     NOT NULL DEFAULT 'coil',
    spec             VARCHAR(200),
    hardness         VARCHAR(16),
    quantity         DECIMAL(12,3)   NOT NULL,
    weight_kg        DECIMAL(12,3),
    unit_price       DECIMAL(12,2),
    amount           DECIMAL(14,2),
    ordered_weight   DECIMAL(12,3)   NOT NULL,
    delivered_weight DECIMAL(12,3)   DEFAULT 0,
    pending_weight   DECIMAL(12,3)   GENERATED ALWAYS AS (ordered_weight - delivered_weight) STORED,
    remark           TEXT
);
CREATE INDEX idx_items_order ON order_items(order_id);
CREATE INDEX idx_items_type ON order_items(material_type);

-- 8.5 入库单表
CREATE TABLE inbound_orders (
    id            BIGSERIAL       PRIMARY KEY,
    inbound_no    VARCHAR(64)     UNIQUE NOT NULL,
    supplier_id   BIGINT          REFERENCES suppliers(id),
    inbound_date  DATE            NOT NULL,
    type          VARCHAR(32)     NOT NULL DEFAULT 'purchase',
    status        VARCHAR(16)     NOT NULL DEFAULT 'draft',
    remark        TEXT,
    created_by    VARCHAR(64),
    created_at    TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMP       NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_inbound_no ON inbound_orders(inbound_no);
CREATE INDEX idx_inbound_supplier ON inbound_orders(supplier_id);
CREATE INDEX idx_inbound_date ON inbound_orders(inbound_date);

-- 8.6 入库明细表
CREATE TABLE inbound_items (
    id              BIGSERIAL       PRIMARY KEY,
    inbound_id      BIGINT          NOT NULL REFERENCES inbound_orders(id) ON DELETE CASCADE,
    product_name    VARCHAR(200)    NOT NULL,
    spec            VARCHAR(200),
    hardness        VARCHAR(16),
    quantity        INTEGER         NOT NULL,
    weight_kg       DECIMAL(12,3)   NOT NULL,
    batch_no        VARCHAR(64),
    internal_no     VARCHAR(64),
    location        VARCHAR(64),
    quality_status  VARCHAR(16)     NOT NULL DEFAULT 'qualified',
    remark          TEXT
);
CREATE INDEX idx_inbound_items_inbound ON inbound_items(inbound_id);
CREATE INDEX idx_inbound_items_batch ON inbound_items(batch_no);

-- 8.7 库存表
CREATE TABLE inventory (
    id                BIGSERIAL       PRIMARY KEY,
    product_name      VARCHAR(200)    NOT NULL,
    spec              VARCHAR(200),
    hardness          VARCHAR(16),
    quantity_available INTEGER        NOT NULL DEFAULT 0,
    weight_available  DECIMAL(12,3)   NOT NULL DEFAULT 0,
    batch_no          VARCHAR(64),
    internal_no       VARCHAR(64),
    location          VARCHAR(64),
    quality_status    VARCHAR(16)     NOT NULL DEFAULT 'qualified',
    supplier_id       BIGINT          REFERENCES suppliers(id),
    inbound_date      DATE,
    inbound_item_id   BIGINT,
    updated_at        TIMESTAMP       NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX idx_inventory_uniq ON inventory(product_name, spec, batch_no, location, quality_status);
CREATE INDEX idx_inventory_batch ON inventory(batch_no);
CREATE INDEX idx_inventory_location ON inventory(location);
CREATE INDEX idx_inventory_status ON inventory(quality_status);

-- 8.8 出库单表
CREATE TABLE outbound_orders (
    id            BIGSERIAL       PRIMARY KEY,
    outbound_no   VARCHAR(64)     UNIQUE NOT NULL,
    order_id      BIGINT          REFERENCES orders(id),
    customer_id   BIGINT          REFERENCES customers(id),
    outbound_date DATE            NOT NULL,
    status        VARCHAR(16)     NOT NULL DEFAULT 'draft',
    remark        TEXT,
    created_by    VARCHAR(64),
    created_at    TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMP       NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_outbound_no ON outbound_orders(outbound_no);
CREATE INDEX idx_outbound_order ON outbound_orders(order_id);
CREATE INDEX idx_outbound_customer ON outbound_orders(customer_id);

-- 8.9 出库明细表
CREATE TABLE outbound_items (
    id              BIGSERIAL       PRIMARY KEY,
    outbound_id     BIGINT          NOT NULL REFERENCES outbound_orders(id) ON DELETE CASCADE,
    inventory_id    BIGINT          REFERENCES inventory(id),
    product_name    VARCHAR(200)    NOT NULL,
    spec            VARCHAR(200),
    hardness        VARCHAR(16),
    quantity        INTEGER         NOT NULL,
    weight_kg       DECIMAL(12,3)   NOT NULL,
    batch_no        VARCHAR(64),
    remark          TEXT
);
CREATE INDEX idx_outbound_items_outbound ON outbound_items(outbound_id);
CREATE INDEX idx_outbound_items_inventory ON outbound_items(inventory_id);

-- 8.10 加工单表
CREATE TABLE processing_orders (
    id               BIGSERIAL       PRIMARY KEY,
    process_no       VARCHAR(64)     UNIQUE NOT NULL,
    order_item_id    BIGINT          REFERENCES order_items(id),
    process_type     VARCHAR(16)     NOT NULL,
    parent_material_inventory_id BIGINT REFERENCES inventory(id),
    parent_spec      VARCHAR(200),
    parent_weight    DECIMAL(12,3),
    child_spec       VARCHAR(200)    NOT NULL,
    output_weight    DECIMAL(12,3)   NOT NULL,
    scrap_weight     DECIMAL(12,3)   DEFAULT 0,
    return_weight    DECIMAL(12,3)   DEFAULT 0,
    process_date     DATE            NOT NULL,
    status           VARCHAR(16)     NOT NULL DEFAULT 'pending',
    remark           TEXT,
    created_by       VARCHAR(64),
    created_at       TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at       TIMESTAMP       NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_processing_no ON processing_orders(process_no);
CREATE INDEX idx_processing_order ON processing_orders(order_item_id);
CREATE INDEX idx_processing_parent ON processing_orders(parent_material_inventory_id);

-- 8.11 二维码标签表
CREATE TABLE qr_labels (
    id                    BIGSERIAL       PRIMARY KEY,
    label_no              VARCHAR(64)     UNIQUE NOT NULL,
    batch_no              VARCHAR(64),
    product_name          VARCHAR(200),
    spec                  VARCHAR(200),
    weight_kg             DECIMAL(12,3),
    qr_content            TEXT            NOT NULL,
    status                VARCHAR(16)     NOT NULL DEFAULT 'unscanned',
    scanned_at            TIMESTAMP,
    source                VARCHAR(16),
    related_transaction_id BIGINT,
    created_at            TIMESTAMP       NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_qr_label_no ON qr_labels(label_no);
CREATE INDEX idx_qr_batch ON qr_labels(batch_no);
CREATE INDEX idx_qr_status ON qr_labels(status);

-- 8.12 系统用户表
CREATE TABLE users (
    id            BIGSERIAL       PRIMARY KEY,
    username      VARCHAR(64)     UNIQUE NOT NULL,
    password_hash VARCHAR(256)    NOT NULL,
    role          VARCHAR(32)     NOT NULL DEFAULT 'operator',
    display_name  VARCHAR(100)    NOT NULL,
    status        VARCHAR(16)     NOT NULL DEFAULT 'active',
    created_at    TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMP       NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_role ON users(role);

-- 8.13 操作日志表
CREATE TABLE operation_logs (
    id             BIGSERIAL       PRIMARY KEY,
    user_id        BIGINT          REFERENCES users(id),
    username       VARCHAR(64),
    action         VARCHAR(32)     NOT NULL,
    resource_type  VARCHAR(32)     NOT NULL,
    resource_id    BIGINT,
    detail         TEXT,
    ip_address     VARCHAR(45),
    created_at     TIMESTAMP       NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_logs_resource ON operation_logs(resource_type, resource_id);
CREATE INDEX idx_logs_user ON operation_logs(user_id);
CREATE INDEX idx_logs_time ON operation_logs(created_at);

-- 8.14 单号计数器表
CREATE TABLE doc_counters (
    id            BIGSERIAL       PRIMARY KEY,
    prefix        VARCHAR(8)      NOT NULL,
    biz_date      DATE            NOT NULL,
    current_seq   INTEGER         NOT NULL DEFAULT 0,
    created_at    TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMP       NOT NULL DEFAULT NOW(),
    UNIQUE (prefix, biz_date)
);

-- =============================================
-- 初始化数据
-- =============================================

-- 默认管理员账户(密码: admin123,需在生产环境修改)
INSERT INTO users (username, password_hash, role, display_name)
VALUES ('admin', '$2b$12$LJ3m4ys3Lk0TSwHnbfOMqOX7Zl5vDqFYpFvqGqG8N9gMBpGpYSFq', 'admin', '系统管理员');

-- 默认操作员账户(密码: operator123)
INSERT INTO users (username, password_hash, role, display_name)
VALUES ('operator', '$2b$12$8pKWkG3x4Bv1pJI4Jw1QVO3KFPEmkVP3LtUjTxHZBx5YXvV6KSJi', 'operator', '操作员');

8.2 单号生成器代码

设计说明:使用 PostgreSQL 数据库序列保证单号唯一性。避免文件计数器方案(/tmp 重启清空、多进程并发冲突)。每天每个前缀对应一条 counter 记录,利用数据库行级锁保证并发安全。

-- 单号计数器表(建表时执行)
CREATE TABLE IF NOT EXISTS doc_counters (
    id            BIGSERIAL       PRIMARY KEY,
    prefix        VARCHAR(8)      NOT NULL,            -- 单号前缀(JK/RK/CK/PG/QR)
    biz_date      DATE            NOT NULL,            -- 业务日期
    current_seq   INTEGER         NOT NULL DEFAULT 0,  -- 当前序号
    created_at    TIMESTAMP       NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMP       NOT NULL DEFAULT NOW(),
    UNIQUE (prefix, biz_date)                           -- 每天每前缀唯一
);
# app/utils/docno.py

from datetime import date
from app import db


def generate_no(prefix: str, business_date: date = None) -> str:
    """
    生成单号。
    格式: {prefix}{YYYYMMDD}{3位序号}
    每天重新计数。使用数据库行级锁保证多进程并发安全。

    Args:
        prefix: 单号前缀(JK=订单 / RK=入库 / CK=出库 / PG=加工 / QR=二维码)
        business_date: 业务日期,默认当天

    Returns:
        单号字符串
    """
    if business_date is None:
        business_date = date.today()

    # UPSERT + 原子递增,利用唯一约束的行级锁防止并发冲突
    from app.models.doc_counter import DocCounter

    result = db.session.execute(
        """
        INSERT INTO doc_counters (prefix, biz_date, current_seq)
        VALUES (:prefix, :biz_date, 1)
        ON CONFLICT (prefix, biz_date)
        DO UPDATE SET current_seq = doc_counters.current_seq + 1,
                      updated_at = NOW()
        RETURNING current_seq
        """,
        {"prefix": prefix, "biz_date": business_date},
    )
    seq = result.scalar_one()
    db.session.commit()

    return f"{prefix}{business_date.strftime('%Y%m%d')}{seq:03d}"

文档结束 · 铭见(上海)智能科技有限公司 · 2026-05-24

本说明书覆盖 Phase 1-4 全阶段开发,含 14 张业务表的 DDL 设计、30+ 个 RESTful API 接口、12 个前端页面规划、二维码和 AI 识别的完整集成方案,以及测试策略和并发安全设计。