# ItemMark 程序设计实现方案

状态：待确认后实施，已吸收 2026-05-09 的确认项

## 1. 整体方案

ItemMark 采用“单项目、多模块、同域名、路径区分”的设计。

默认部署域名：

```text
https://code.zestrade.com
```

模块路径：

| 模块 | 路径前缀 | 扫码页 | 扫码逻辑 |
|---|---|---|---|
| 对外物料信息模块 | `/material-info` | `/material/{code}` 和 `/material-info/material/{code}` | 查询物料基础信息 |
| 武平模块 | `/wuping` | `/wuping/material/{code}` | 查询库存台账 |

兼容策略：

- 旧项目 1 已经正常运行，旧二维码和模板优先兼容。
- `/material/{code}` 保留给对外物料信息模块，避免旧标签失效。
- 武平模块所有二维码都加 `/wuping` 前缀，避免和旧二维码冲突。

## 2. 目标目录结构

```text
ItemMark/
├── README.md
├── .env
├── .env.example
├── docker-compose.yml
├── docs/
├── certs/
│   └── code.zestrade.com/
├── config/
│   ├── nginx.conf
│   ├── modules/
│   │   ├── material-info.env
│   │   └── wuping.env
│   └── templates/
│       ├── material-info/
│       │   └── label_config.xlsx
│       └── wuping/
│           └── label_config.xlsx
├── data/
│   ├── admin/
│   │   └── itemmark_admin.db
│   ├── material-info/
│   │   └── itemmark.db
│   └── wuping/
│       └── itemmark.db
├── tools/
│   └── create_label_config.py
├── sync/
│   ├── Dockerfile
│   ├── scheduler.py
│   ├── sync_runner.py
│   ├── db_u8.py
│   ├── db_sqlite.py
│   ├── qr_gen.py
│   ├── logger.py
│   ├── requirements.txt
│   └── freetds.conf
└── web/
    ├── Dockerfile
    ├── main.py
    ├── settings.py
    ├── requirements.txt
    ├── db/
    │   ├── admin_db.py
    │   ├── material_db.py
    │   └── u8_ledger_db.py
    ├── routers/
    │   ├── admin.py
    │   ├── api.py
    │   ├── material_info.py
    │   └── wuping.py
    ├── services/
    │   ├── module_config_service.py
    │   ├── material_service.py
    │   ├── ledger_service.py
    │   ├── label_template_service.py
    │   ├── qr_service.py
    │   └── sync_service.py
    └── templates/
        ├── admin/
        │   ├── index.html
        │   ├── modules.html
        │   └── sync.html
        ├── material_info/
        │   ├── detail.html
        │   ├── print.html
        │   ├── batch.html
        │   └── batch_print.html
        └── wuping/
            ├── ledger_detail.html
            ├── print.html
            ├── batch.html
            └── batch_print.html
```

## 3. 配置设计

### 3.1 全局配置

`.env` 保存全局部署配置：

```ini
APP_NAME=ItemMark
BASE_DOMAIN=https://code.zestrade.com
ADMIN_DB_PATH=./data/admin/itemmark_admin.db
WEB_HOST=0.0.0.0
WEB_PORT=18089
HOST_WEB_PORT=18089
LOG_LEVEL=INFO
```

说明：

- 可以从旧项目复制真实 `.env` 作为初始配置来源。
- 整合后会把模块级 U8 配置拆到模块配置中。

### 3.2 模块配置

每个模块独立配置。配置来源优先级：

1. 后台页面保存到 `data/admin/itemmark_admin.db` 的模块配置。
2. `config/modules/{module}.env`。
3. 代码默认值。

模块配置字段：

| 字段 | 说明 | 默认值 |
|---|---|---|
| `module_key` | 模块标识 | `material-info` / `wuping` |
| `module_name` | 模块名称 | 对外物料信息 / 武平 |
| `base_path` | 模块路径前缀 | `/material-info` / `/wuping` |
| `qr_base_url` | 二维码前缀 | `https://code.zestrade.com/...` |
| `sqlite_path` | 模块 SQLite 路径 | `./data/{module}/itemmark.db` |
| `u8_host` | U8 地址 | `192.168.1.135` |
| `u8_port` | U8 端口 | `1433` |
| `u8_database` | U8 账套 | 对外物料信息默认 `UFDATA_104_2018`，武平默认 `UFDATA_105_2019` |
| `u8_user` | U8 账号 | `testreadonly` |
| `u8_password` | U8 密码 | 从旧配置复制或页面维护 |
| `template_path` | 标签模板 | `./config/templates/{module}/label_config.xlsx` |
| `detail_type` | 扫码详情类型 | `material_info` / `ledger` |
| `ledger_enabled` | 是否启用台账 | 武平为 `true` |

### 3.3 推荐初始模块配置

对外物料信息模块：

```ini
MODULE_KEY=material-info
MODULE_NAME=对外物料信息
BASE_PATH=/material-info
QR_BASE_URL=https://code.zestrade.com
SQLITE_PATH=./data/material-info/itemmark.db
U8_HOST=192.168.1.135
U8_PORT=1433
U8_DATABASE=UFDATA_104_2018
U8_USER=testreadonly
U8_PASSWORD=
TEMPLATE_PATH=./config/templates/material-info/label_config.xlsx
DETAIL_TYPE=material_info
LEDGER_ENABLED=false
```

武平模块：

```ini
MODULE_KEY=wuping
MODULE_NAME=武平
BASE_PATH=/wuping
QR_BASE_URL=https://code.zestrade.com/wuping
SQLITE_PATH=./data/wuping/itemmark.db
U8_HOST=192.168.1.135
U8_PORT=1433
U8_DATABASE=UFDATA_105_2019
U8_USER=testreadonly
U8_PASSWORD=
TEMPLATE_PATH=./config/templates/wuping/label_config.xlsx
DETAIL_TYPE=ledger
LEDGER_ENABLED=true
```

### 3.4 物料主数据和账套口径

物料基础信息在 104、105 等账套之间通用。104 账套是集团主账套、贸易账套，首版作为默认物料主数据来源；105 账套是子公司工厂账套，武平模块使用 105 查询出入库和库存结存。

实现策略：

- `material-info` 默认从 104 同步物料基础信息。
- `wuping` 默认也可以同步物料基础信息到自己的模块库，但同步 SQL 和字段结构与 104 一致。
- 首版确认保留模块 SQLite 隔离，降低影响项目 1 已调试模板和旧二维码的风险。
- 如果后续希望减少重复数据，可以再增加共享物料主数据表。
- 台账查询永远使用当前模块配置的账套，武平默认 `UFDATA_105_2019`。

## 4. 路由设计

### 4.1 页面路由

全局：

```text
GET /                         首页/模块入口
GET /admin                    管理入口
GET /admin/sync               同步管理页面
GET /admin/modules            模块配置页面
```

对外物料信息模块：

```text
GET /material/{code}                         兼容旧二维码，展示物料信息
GET /material-info/material/{code}           新模块路径，展示物料信息
GET /material-info/batch                     批量打印选择
GET /material-info/print/{code}              单张打印
GET /material-info/batch/print?codes=...     批量打印
GET /material-info/qr/{code}                 二维码图片
```

武平模块：

```text
GET /wuping/material/{code}                  展示库存台账
GET /wuping/batch                            批量打印选择
GET /wuping/print/{code}                     单张打印
GET /wuping/batch/print?codes=...            批量打印
GET /wuping/qr/{code}                        二维码图片
```

### 4.2 API 路由

```text
GET  /api/modules
GET  /api/{module}/material/{code}
GET  /api/{module}/materials?keyword=&limit=&offset=
POST /api/{module}/materials/by-codes
GET  /api/{module}/label-config
GET  /api/{module}/sync/status
POST /api/{module}/sync/run
GET  /api/{module}/sync/config
PUT  /api/{module}/sync/config
GET  /api/health
```

武平模块：

```text
GET /api/wuping/material/{code}/ledger?start_date=&end_date=
```

兼容旧项目 1：

```text
GET /api/material/{code}
GET /api/materials
```

## 5. 二维码设计

二维码必须由模块决定。

对外物料信息模块：

```text
https://code.zestrade.com/material/{material_code}
```

说明：

- 使用旧项目 1 的扫码路径。
- 最大程度兼容项目 1 已经打印出去的标签和已调试好的模板。

武平模块：

```text
https://code.zestrade.com/wuping/material/{material_code}
```

说明：

- 明确带 `/wuping` 前缀。
- 扫码进入库存台账页。

二维码图片接口也按模块区分：

```text
/material-info/qr/{material_code}
/wuping/qr/{material_code}
```

## 6. 标签模板设计

每个模块独立读取 Excel 配置：

```text
config/templates/material-info/label_config.xlsx
config/templates/wuping/label_config.xlsx
```

模板服务接口：

- `get_label_config(module_key)`
- `get_visible_fields(module_key)`
- `get_template_path(module_key)`

实施策略：

- 对外物料信息模块直接复制项目 1 的 `config/label_config.xlsx`。
- 武平模块复制项目 2 的 `config/label_config.xlsx`。
- 代码读取模板时传入 `module_key`，避免两个模块共享同一个模板。

## 7. 数据库设计

### 7.1 模块业务库

每个模块一个 SQLite：

```text
data/material-info/itemmark.db
data/wuping/itemmark.db
```

首版业务表沿用：

```text
material_label_items
```

原因：

- 两个旧项目表结构一致。
- 降低迁移风险。
- 便于复制旧数据库作为参考或临时回滚。

### 7.2 管理库

统一管理库：

```text
data/admin/itemmark_admin.db
```

建议表：

```sql
module_configs(
  module_key TEXT PRIMARY KEY,
  module_name TEXT NOT NULL,
  base_path TEXT NOT NULL,
  qr_base_url TEXT NOT NULL,
  sqlite_path TEXT NOT NULL,
  template_path TEXT NOT NULL,
  detail_type TEXT NOT NULL,
  ledger_enabled INTEGER NOT NULL,
  u8_host TEXT NOT NULL,
  u8_port INTEGER NOT NULL,
  u8_database TEXT NOT NULL,
  u8_user TEXT NOT NULL,
  u8_password TEXT,
  updated_at TEXT
);

sync_configs(
  module_key TEXT PRIMARY KEY,
  enabled INTEGER NOT NULL,
  mode TEXT NOT NULL,
  cron_expr TEXT,
  interval_minutes INTEGER,
  run_times TEXT,
  updated_at TEXT
);

sync_runs(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  module_key TEXT NOT NULL,
  status TEXT NOT NULL,
  started_at TEXT,
  finished_at TEXT,
  extracted_count INTEGER DEFAULT 0,
  inserted_count INTEGER DEFAULT 0,
  updated_count INTEGER DEFAULT 0,
  failed_count INTEGER DEFAULT 0,
  elapsed_seconds REAL,
  error_message TEXT
);
```

## 8. 同步设计

### 8.1 同步逻辑

同步核心逻辑参数化：

```text
run_sync(module_config)
```

流程：

1. 读取模块配置。
2. 连接模块指定的 U8 数据库。
3. 抽取物料基础资料。默认物料主数据口径来自 104 主账套；模块可按自己的配置改为其他账套。
4. 按模块二维码规则生成 `qr_url`。
5. 写入模块自己的 SQLite。
6. 写入 `sync_runs` 执行记录。

### 8.2 自动同步

`itemmark-sync` 容器负责自动同步：

- 启动时读取 `sync_configs`。
- 每分钟检查配置是否变化。
- 根据每个模块自己的计划触发同步。
- 同一模块运行中时跳过新的触发。

### 8.3 手动同步

Web 页面点击“立即同步”：

- 调用 `POST /api/{module}/sync/run`。
- 后端创建后台任务执行 `run_sync(module_config)`。
- 前端轮询 `GET /api/{module}/sync/status` 查看进度。

实现注意：

- 手动同步和自动同步共用同一套同步锁。
- 同一模块不能并发同步。
- 不同模块可以并行同步。

## 9. U8 台账设计

武平模块沿用旧项目 2 的 U8 台账 SQL：

- 期初结存
- 采购入库单
- 调拨入库单
- 调拨出库单
- 材料出库单
- 销售出库单

要求：

- 使用武平模块自己的 U8 配置。
- 武平模块默认账套为 `UFDATA_105_2019`，可在后台改为其他账套。
- SQL Server 2008 R2 兼容，不使用 `OFFSET/FETCH`。
- 查询失败时页面降级显示基础信息。

## 9.1 后台权限策略

首版后台页面不做登录保护：

- `/admin`
- `/admin/sync`
- `/admin/modules`

原因：当前需求明确不需要登录。

风险控制建议：

- 当前确认暂不做 Nginx IP 白名单。
- 后续如后台暴露风险变高，可再通过内网访问、VPN 或 Nginx IP 白名单限制后台路径。
- 后端日志不得打印 U8 密码。
- 页面展示密码时默认脱敏，只在编辑时允许重新填写。

## 10. Docker 和 Nginx 设计

Docker 服务：

```text
itemmark-web
itemmark-sync
```

端口：

```text
宿主机 18089 -> 容器 18089
```

端口约束：

- ItemMark Web 应用自身不使用 `8000`、`8080`、`3000`、`5000` 等常用端口。
- 首版统一使用 `18089` 作为容器监听端口和宿主机映射端口。
- `https://code.zestrade.com` 对外 HTTPS 访问仍由 Nginx 提供；非常用端口要求只约束 ItemMark 应用服务端口。

Nginx：

- 使用 `https://code.zestrade.com`。
- 根路径反向代理到 `itemmark-web`。
- 证书和私钥可以复制旧项目已有文件。

示例：

```nginx
server {
    listen 443 ssl;
    server_name code.zestrade.com;

    ssl_certificate     ./certs/code.zestrade.com/code.zestrade.com.crt;
    ssl_certificate_key ./certs/code.zestrade.com/code.zestrade.com.key;

    location / {
        proxy_pass http://127.0.0.1:18089;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

## 11. 旧项目资料迁移策略

允许复制：

- 旧项目真实 `.env`。
- 旧项目数据库文件。
- 旧项目证书和私钥。
- 项目 1 已调试好的标签配置。
- 项目 2 已实现的库存台账查询代码和模板。

迁移原则：

- 旧项目 1 正在生产运行，整合时优先保证旧二维码路径和标签打印效果不变。
- 旧数据库可复制到 `data/legacy/` 备份，也可作为初始检查数据。
- 首次正式运行仍重新从 U8 同步，避免新旧数据混杂。
- 证书路径在部署文档中明确，Docker 项目内可保存证书副本。

## 11.1 相对路径约束

所有运行时路径使用相对路径，配置中不得写入开发机绝对路径或服务器绝对路径。

允许的路径形式：

```text
./data/admin/itemmark_admin.db
./data/material-info/itemmark.db
./data/wuping/itemmark.db
./config/modules/material-info.env
./config/templates/material-info/label_config.xlsx
./certs/code.zestrade.com/code.zestrade.com.key
./logs/sync.log
```

不允许的路径形式：

```text
/Users/mac/Desktop/ItemMark/...
/opt/ItemMark/...
/app/data/...
```

Docker 容器内部路径可以由 Dockerfile 和 Compose 挂载实现，但业务配置和代码读取入口应基于项目相对路径解析。

## 12. 实施步骤

0. 编码前按 `2026-04-20-LLM-写代码规范.md` 写明假设、成功标准和验证方式。
1. 按目标结构创建 ItemMark 项目。
2. 复制项目 1 模板和物料信息扫码页面能力到 `material-info` 模块。
3. 复制项目 2 台账查询能力到 `wuping` 模块。
4. 建立模块配置服务和管理库。
5. 改造同步逻辑为模块参数化。
6. 实现同步管理页面和 API。
7. 实现模块化二维码生成。
8. 实现模块化标签配置读取。
9. 保留 `/material/{code}` 兼容路由。
10. 完成 Docker Compose 和 Nginx 配置，应用端口使用 `18089`。
11. 执行语法检查、Docker 配置检查和本地功能验证。

## 13. 验证命令

```bash
python3 -m py_compile sync/*.py web/*.py web/routers/*.py web/services/*.py web/db/*.py
python3 tools/create_label_config.py --module material-info
python3 tools/create_label_config.py --module wuping
docker-compose config
```

功能验证：

```text
https://code.zestrade.com/
https://code.zestrade.com/admin/sync
https://code.zestrade.com/material-info/batch
https://code.zestrade.com/material/{code}
https://code.zestrade.com/wuping/batch
https://code.zestrade.com/wuping/material/{code}
```

## 14. 已识别风险

1. `/material/{code}` 必须保留给旧项目 1 兼容，武平模块不能占用该路径。
2. 两个模块使用同一域名时，二维码生成必须严格按模块配置，不能共用旧的 `QR_BASE_URL`。
3. 后台页面保存 U8 密码，需要避免在普通日志中打印密码。
4. 手动同步和自动同步必须加锁，避免同一模块并发写 SQLite。
5. 项目 1 已在 Linux OS 正常运行，整合部署前应备份旧目录、旧数据库和旧 Nginx 配置。
6. 旧证书私钥可以复制，但部署文档要写清证书位置和权限。
