# 集物 **Repository Path**: amning/Gatherio ## Basic Information - **Project Name**: 集物 - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-05-11 - **Last Updated**: 2026-07-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Gatherio / 集物 一个面向个人场景的资产管理系统,支持 **实物资产、虚拟资产、消耗品** 三类资产的统一管理,并提供提醒、统计、待购买、识别导入、备份恢复和升级能力。 当前代码线版本:`v2.0.1` ## 1. 项目定位 Gatherio 主要解决这些问题: - 把家里或个人名下的各类物品统一整理起来 - 区分不同资产类型的管理方式 - 跟踪价格、使用状态、提醒和处置结果 - 管理待购买物品,并在购入后转为正式资产 - 通过订单截图识别、Excel 导入等方式减少手工录入 系统当前已经支持三类主资产: - **实物资产(physical)**:手机、电脑、家电、家居、服饰、收藏等 - **虚拟资产(virtual)**:会员、软件授权、域名、云服务、数字权益等 - **消耗品(consumable)**:药品、美妆、日用品、食品耗材、办公耗材等 ## 2. 当前能力概览 ### 已实现 - JWT 登录/注册/个人资料维护 - 三类资产独立入口与独立状态体系 - 资产创建、编辑、详情、删除、处置、附件上传 - 消耗品主档 + 采购批次 + 消耗流水模型 - 消耗品批次列表、新增/编辑批次、库存调整、自动/手动消耗、低库存提醒、批次到期提醒 - 消耗品批次支持 AI 识别账单回填,并可从列表页直接进入新增批次 - 提醒中心支持快捷筛选、按类型 / 状态 / 资产类型筛选;到期提醒区分 1个月内到期 / 1星期内到期 / 已过期,低库存按消耗品主档阈值与批次汇总剩余量判断,并支持虚拟续费/低库存等提醒一键加入待购买 - 虚拟资产主档 + 续费记录、到期提醒 - 资产分类/标签/位置/渠道/状态的用户级管理 - 统计分析、事件统计、聚合统计 - 待购买清单与转为资产 - Excel 导入、订单截图识别导入;Excel 模板支持实物资产、虚拟资产、消耗品三类字段 - 数据备份、恢复、系统升级包上传(已包含消耗品批次与流水) - 用户级设置与界面配置 ### 已实现与后续方向 - 资产属性差异继续以 **`asset_type` 为主**,不按小分类拆独立字段体系 - 小分类继续保留,但后续建议按主资产类型分组维护 - 消耗品已升级为: - **一个主物品 + 多条采购批次记录** - 库存提醒看总量 - 到期提醒看批次 - 消耗时默认自动扣减,同时允许手动调整批次 > 当前代码已经落地消耗品“主物品 + 批次 + 流水”模型;后续主要是继续优化主档录入页、列表页和更多批次视图体验。 ## 3. 技术栈 ### 前端 - Vue 3 - Vue Router 4 - Pinia - Element Plus - ECharts - Axios - Vite ### 后端 - Node.js - Express 4 - better-sqlite3 - JWT - multer - exceljs - archiver - unzipper ### 存储与部署 - SQLite 数据库 - 本地文件存储(上传文件、备份文件) - Docker Compose 部署 - Nginx 提供前端静态资源与反向代理 ## 4. 仓库结构 ```text Gatherio/ ├─ backend/ # Express + SQLite 后端 │ └─ src/ │ ├─ index.js # 主要 API、认证、业务逻辑 │ └─ db.js # SQLite 初始化、建表、迁移、默认数据 ├─ frontend/ # Vue 3 前端 │ ├─ public/ │ ├─ src/ │ │ ├─ pages/ # 页面级功能 │ │ ├─ components/ # 通用组件 │ │ ├─ stores/ # Pinia 状态 │ │ ├─ api.js # Axios 实例与统一请求逻辑 │ │ └─ router.js # 路由定义与登录守卫 │ └─ vite.config.js ├─ storage/ # 数据库、上传文件、导出与备份(运行后生成/使用) ├─ docker-compose.yml # Docker 部署入口 ├─ package.json # workspace 根脚本 └─ README.md ``` ## 5. 本地开发 ### 安装依赖 ```bash npm install ``` ### 启动开发环境 ```bash npm run dev:frontend npm run dev:backend ``` 默认开发行为: - 前端:Vite 开发服务器 - 后端:Node `--watch` - 前端通过代理访问 `/api`、`/uploads`、`/exports` ### 生产构建 ```bash npm run build npm run start ``` ### Android 客户端 Android 端复用现有 Vue 前端,通过 Capacitor 打包,功能和 Web 端保持一致。 ```bash npm run android:sync # 构建前端并同步到 Android 工程 npm run android:open # 用 Android Studio 打开工程 npm run android:run # 同步后运行到已连接设备/模拟器 ``` 首次运行前需要确保: - 本机已安装 Android Studio、Android SDK 和 JDK。 - 后端服务可被手机访问;模拟器可用 `http://10.0.2.2:4001`,真机请填写电脑局域网地址。 - 登录/注册页的“服务器地址”用于配置 Android 端后端地址;浏览器端可留空继续使用 `/api` 代理。 ## 6. Docker 部署 ```bash docker compose up -d ``` 当前 `docker-compose.yml` 里: - `backend` 提供 API 与文件服务 - `frontend` 通过 Nginx 提供页面 - 默认把外部端口映射到 `80` - 使用 `./storage` 持久化数据库与文件 ### 部署注意事项 当前 Compose 文件中包含默认 JWT Secret 示例值,正式部署时应自行覆盖: - `JWT_SECRET` - `PORT`(如有需要) ## 7. 核心业务模型 ## 7.1 资产类型(asset_type) 系统当前以 `asset_type` 作为最核心的字段分层维度: ### 实物资产(physical) 适合管理: - 数码 - 家电 - 家居 - 服饰 - 工具 - 收藏等 重点字段: - `location_id` - `depreciation_method` - `warranty_until` - `expiry_date` - `disposal_date` - `resale_price` 重点行为: - 折旧 - 保修提醒 - 闲置/维修/出售/退役等状态流转 ### 虚拟资产(virtual) 适合管理: - 会员订阅 - 软件授权 - 域名 - 云服务 - 数字内容 - 账号服务等 当前模型: - **主档 `assets`**:名称、分类、服务商、账号标识、激活日期、订阅周期、自动续费、访问地址、授权码、备注 - **续费记录 `virtual_renewal_records`**:每次购买/续费的购买日期、有效期、金额、渠道、订单号、备注 - **汇总字段**:主档上的最近购买、当前有效期、累计费用等用于展示当前状态,不再作为历史事实直接维护 当前重点行为: - 新建虚拟物品时可录入首期订阅信息,也可以先建立空主档 - 后续续费统一通过“记录续费”生成续费记录,而不是直接覆盖主档购买信息 - 当前有效期按最近一条续费记录汇总展示 - 列表页和详情页优先展示服务商、账号标识、订阅周期、自动续费、当前有效期、累计费用 - 续费提醒、到期提醒、访问地址与授权信息维护 ### 消耗品(consumable) 适合管理: - 药品 - 美妆 - 日用品 - 食品耗材 - 办公耗材等 当前模型: - **主档 `assets`**:名称、分类、默认单位、默认位置、低库存阈值、标签、备注、图片 - **采购批次 `consumable_batches`**:品牌、规格/型号、批次数量、剩余数量、位置、价格、渠道、订单号、生产日期、保质期、开封日期 - **消耗流水 `consumable_transactions`**:消耗、库存调整、手动/自动扣减历史 当前重点行为: - 新建消耗品主档后按批次入库 - 消耗默认按先到期先出(FEFO),也支持手动指定批次 - 低库存提醒按主档阈值 + 汇总剩余量判断 - 过期提醒按仍有剩余量的批次逐条生成 - 详情页集中展示库存汇总、批次与流水,不再把主档当单次采购记录 ## 7.2 小分类(category) `category` 当前仍然是资产的辅助分类维度,不是字段模板驱动器。 默认分类包括: - 数码 - 家电 - 家居 - 美妆 - 服饰 - 药品 - 日用品 当前分类能力: - 可由用户维护名称与图标 - 可用于筛选、统计与详情展示 - 可与不同主资产类型搭配使用 后续建议方向: - 按主资产类型维护不同的小分类集合 - 但仍然不让小分类直接决定独立字段集合 ## 7.3 待购买(Pending Purchases) 系统提供“待购买”模块,用于在真正购入前管理计划物品: - 记录名称、分类、品牌、型号、预估价格、计划购买时间、优先级等 - 支持归档与批量删除 - 支持一键“转为资产” - 转换时可补齐不同类型所需字段 ## 8. 前端页面说明 路由定义见 [frontend/src/router.js](frontend/src/router.js)。 主要页面包括: - `/assets/physical`:实物资产列表 - `/assets/virtual`:虚拟资产列表 - `/assets/consumable`:消耗品列表 - `/assets/new`:新建资产 - `/assets/:id`:资产详情 - `/assets/:id/edit`:编辑资产 - `/pending-purchases`:待购买清单 - `/reminders`:提醒中心 - `/stats`:统计分析 - `/settings`:系统设置 - `/backup`:数据备份 - `/profile`:个人中心 - `/login` / `/register`:认证入口 ### 界面特点 - `App.vue` 提供统一应用壳、响应式侧边栏和移动端头部 - 资产导航按三类主资产拆分 - 提醒中心与浏览器通知集成 - 系统设置里集中管理: - 基础数据 - OCR/LLM 识别配置 - 侧边栏与菜单配置 ## 9. 后端 API 模块说明 后端主要逻辑集中在 [backend/src/index.js](backend/src/index.js),目前是单文件 Express 应用。 主要 API 分区: ### 认证与用户 - `/api/auth/register` - `/api/auth/login` - `/api/auth/me` - `/api/auth/profile` - `/api/auth/password` ### 资产与附件 - `/api/assets` - `/api/assets/:id` - `/api/assets/:id/image` - `/api/assets/:id/order-screenshot` - `/api/assets/:id/attachments` - `/api/assets/:id/logs` ### 资产专属动作 - `/api/assets/:id/quick-action` - `/api/assets/:id/consume` - `/api/assets/:id/renew` ### 元数据 - `/api/meta` - `/api/categories` - `/api/tags` - `/api/locations` - `/api/statuses` - `/api/channels` ### 统计 - `/api/dashboard/summary` - `/api/stats/aggregation` - `/api/stats/events` ### 待购买 - `/api/pending-purchases` - `/api/pending-purchases/:id/convert` ### 导入与识别 - `/api/imports/template` - 模板包含资产类型列,支持 `physical` / `virtual` / `consumable` 或中文类型 - 消耗品字段包含数量、剩余数量、单位、低库存阈值、生产日期、保质期、开封日期 - 虚拟资产字段包含服务商、账号标识、有效期开始、订阅周期、到期日、自动续费、访问地址、授权码 - `/api/imports/assets` - `/api/imports/assets/confirm` - 实物资产导入为普通资产 - 消耗品同名主档已存在时追加采购批次 - 虚拟资产同名主档已存在时追加续费记录 - `/api/recognition/order-image` - `/api/recognition/confirm/:id` - 实物资产:确认后创建资产 - 虚拟资产:同名服务已存在时追加续费记录,否则创建主档和首期记录 - 消耗品:同名主档已存在时追加采购批次,否则创建主档和首批批次 - `/api/recognition/jobs` - `/api/assets/:id/batches` - `/api/assets/:id/batches/recognize-order-image` ### 配置、备份、系统升级 - `/api/config` - `/api/backup/list` - `/api/backup/create` - `/api/backup/restore` - `/api/system/version` - `/api/system/upgrade` ## 10. 数据存储说明 ### SQLite 数据库文件位于: - `storage/gatherio.db` 主要表包括: - `assets` - `categories` - `tags` - `locations` - `statuses` - `channels` - `asset_tags` - `users` - `settings` - `reminders` - `import_jobs` - `recognition_jobs` - `attachments` - `operation_logs` - `pending_purchases` - `pending_purchase_tags` - `consumable_batches` - `consumable_transactions` ### 文件存储 主要目录: - `storage/uploads/`:资产图片、订单截图、附件、识别上传图 - `storage/exports/`:导出文件、备份文件 ## 11. 消耗品批次能力(当前实现与后续迭代) 这是当前系统里最重要的一块产品能力之一。 ### 当前实现 当前消耗品已经不是单纯的一条资产记录,而是三层模型: - **主物品 `assets`**:负责名称、分类、单位、低库存阈值和聚合展示 - **采购批次 `consumable_batches`**:记录每次买入、数量、剩余、保质期、订单号等 - **消耗流水 `consumable_transactions`**:记录消耗、库存调整等历史过程 当前已经支持: - **一个主物品**:例如“纸巾” - **多条采购批次**:每次购买新增一条记录 - **库存提醒看总量**:按所有批次剩余量汇总 - **到期提醒看批次**:单独识别哪一批快到期 - **阶段提醒更细**:统一生成 1个月内到期 / 1星期内到期 / 已过期 三档提醒 - **低库存按阈值判断**:主档 `low_stock_threshold` 与汇总剩余量比较,不再硬编码 - **统计金额按批次口径汇总**:采购总价、剩余估值、已耗尽/临期/过期批次等都按批次数据计算 - **消耗默认自动扣减**:优先 FEFO / 最早到期批次 - **保留手动调整能力**:允许手动指定扣减批次 - **库存调整**:可直接修正某一批次剩余数量,并自动写入流水 - **支持账单识别新增批次**:可在详情页上传账单自动回填购买日期、价格、渠道、订单号等 - **提醒中心支持筛选**:可按提醒类型、处理状态、资产类型快速过滤 ### 当前模型价值 这样做以后,消耗品管理会更自然: - 买的时候记一笔 - 用的时候扣一笔 - 快没了提醒补货 - 某一批快过期时单独提醒 - 库存盘点时可按批次修正 ### 纸巾示例 ### 纸巾示例 主物品: - 名称:纸巾 - 小分类:日用品 - 单位:包 - 低库存阈值:4 批次 A: - 购买日期:2026-05-01 - 数量:10 - 剩余:2 - 订单号:JD-001 - 保质期:2027-05-01 批次 B: - 购买日期:2026-06-10 - 数量:6 - 剩余:6 - 订单号:TB-002 - 保质期:2027-06-10 系统应展示: - 总库存:8 包 - 当前状态:库存正常 - 下一到期批次:A 批次 ### 11.1 数据库设计(当前实现) 当前 `assets` 表继续承载消耗品主档,同时已经新增“批次”和“消耗流水”两层数据,并对 `reminders` 做了批次维度增强。 #### A. 消耗品批次表 `consumable_batches` 用途:记录每一次采购/入库形成的批次。 建议字段: - `id`:主键 - `asset_id`:所属主物品,对应 `assets.id` - `user_id`:用户隔离 - `batch_code`:批次编号,可为空;默认可由系统生成 - `source_type`:来源类型,如 `purchase` / `manual` / `import` - `purchase_date`:购买日期 - `quantity`:本批次购入数量 - `remaining_quantity`:本批次当前剩余数量 - `unit`:单位,默认跟主物品一致,但允许单独存档 - `unit_price`:单价,可为空 - `total_price`:总价,可为空 - `purchase_channel`:购买渠道 - `order_no`:订单号 - `production_date`:生产日期 - `expiry_date`:保质期/有效期 - `opened_at`:开封日期 - `status`:批次状态,建议值:`active` / `exhausted` / `expired` / `discarded` - `notes`:备注 - `created_at` - `updated_at` 建议索引: - `(asset_id, purchase_date)`:按物品查看批次时间线 - `(asset_id, expiry_date)`:找最早到期批次 - `(user_id, status)`:做用户级提醒或筛选 #### B. 消耗流水表 `consumable_transactions` 用途:记录每次消耗、补录、调整,形成可追溯的库存账本。 建议字段: - `id`:主键 - `asset_id`:所属主物品 - `batch_id`:关联批次;自动扣减时可记录实际扣减到哪一批 - `user_id`:用户隔离 - `type`:流水类型,建议值:`consume` / `adjust` / `rollback` - `amount`:本次变化数量,统一记正数 - `unit`:单位 - `consume_date`:发生时间/业务时间 - `mode`:`auto` / `manual` - `notes`:备注 - `created_at` 建议索引: - `(asset_id, consume_date)`:展示某个消耗品的使用历史 - `(batch_id, consume_date)`:追踪某批次被消耗的过程 #### C. 对现有 `assets` 表的角色调整 `assets` 里的消耗品字段保留,但语义调整为“主档汇总/兼容字段”: - `unit`:继续作为主物品默认单位 - `low_stock_threshold`:继续作为低库存阈值 - `remaining_quantity`:改为**主物品总剩余量缓存** - `expiry_date`:改为**最近到期批次日期缓存**,便于兼容现有提醒和列表展示 - `opened_at` / `production_date`:主档上可弱化,仅在确有全局意义时保留展示 - `quantity`:不再作为核心事实来源,后续前端应逐步淡化其直接编辑入口 这意味着: - **事实数据以批次表为准** - **`assets` 里保留的是汇总值和兼容值** #### D. 对 `reminders` 表的增强建议 当前 `reminders` 只有 `asset_id` 维度。为了支持“某一批次快到期”,建议增加: - `batch_id`:可为空,指向 `consumable_batches.id` 规则: - 低库存提醒:`batch_id = null`,仍然针对主物品 - 批次到期提醒:`batch_id = 某批次 id` 这样现有提醒体系可以继续沿用,不需要重做一套提醒表。 ### 11.2 数据聚合与状态计算规则 消耗品主档的关键数据不再手填,而是由批次自动聚合。 #### 主档聚合字段建议 对每个消耗品主档,后端在批次变更后统一重算: - `remaining_quantity = 所有 active 批次 remaining_quantity 之和` - `expiry_date = 最早未完成批次的 expiry_date` - `status`:按总库存和批次状态自动判断 #### 状态判断建议 1. **已耗尽** - 所有有效批次剩余量之和为 `0` 2. **低库存** - 总剩余量 `<= low_stock_threshold` - 且总剩余量 `> 0` 3. **已过期** - 可作为批次状态存在 - 主物品层面不建议轻易自动切成“已过期”,除非全部剩余批次都不可使用 4. **库存正常** - 总剩余量大于阈值,且至少有一个可用批次 #### 批次扣减策略 已确认的产品决策: - 默认自动扣减 - 同时允许手动指定批次 建议实现成: - **自动模式**:优先按 `expiry_date ASC`,若无保质期则按 `purchase_date ASC` - **手动模式**:用户显式传入 `batch_id` 自动模式适合: - 纸巾 - 洗衣液 - 清洁用品 手动模式适合: - 药品 - 美妆 - 食品原料 ### 11.3 API 设计(当前实现) 当前已经在保留 `/api/assets/:id/consume` 的基础上,扩展了一组批次相关接口。 #### A. 主物品批次列表 ```http GET /api/assets/:id/batches ``` 用途: - 获取某个消耗品主档下的所有批次 - 按购买时间或到期时间排序 返回建议: - `items` - `summary`:总剩余量、最近到期日、低库存标记、批次数 #### B. 新增采购批次 ```http POST /api/assets/:id/batches ``` 请求体建议: ```json { "purchase_date": "2026-06-10", "quantity": 6, "remaining_quantity": 6, "unit": "包", "total_price": 39.9, "purchase_channel": "淘宝", "order_no": "TB-002", "production_date": "2026-05-01", "expiry_date": "2027-06-10", "opened_at": "", "notes": "促销补货" } ``` 当前行为: - 只允许 `asset_type=consumable` 的资产调用 - 创建批次后自动重算主档汇总字段 - 自动补一条操作日志,如“新增采购批次” - 如有到期时间,自动重建该资产提醒 #### C. 编辑批次 ```http PUT /api/assets/:id/batches/:batchId ``` 适用场景: - 修正数量 - 补录保质期 - 补录开封日期 - 修改订单号/价格/渠道 行为建议: - 修改后重算主档汇总字段 - 若把 `remaining_quantity` 改为 `0`,批次状态自动转 `exhausted` #### D. 批次详情(可选) ```http GET /api/assets/:id/batches/:batchId ``` 如果详情页直接使用列表数据,也可以不单独做。 #### E. 记录消耗(升级现有 consume 接口) 继续保留现有接口: ```http POST /api/assets/:id/consume ``` 建议请求体升级为: ```json { "amount": 5, "mode": "auto", "batch_id": null, "consume_date": "2026-06-18", "notes": "家庭日常使用" } ``` 规则: - `mode=auto`:后端自动分配到一个或多个批次 - `mode=manual`:要求传 `batch_id` - 若单批次不足且是自动模式,允许跨批次扣减 - 每次实际扣减都写入 `consumable_transactions` - 完成后重算主档库存、状态、提醒 #### F. 查询消耗流水 ```http GET /api/assets/:id/consumptions ``` 用途: - 在详情页展示使用历史 - 让用户知道库存是如何减少的 返回建议: - `items` - 每条含:数量、批次、模式、时间、备注 #### G. 手动库存调整(可选但推荐) ```http POST /api/assets/:id/stock-adjust ``` 用途: - 盘点修正 - 丢弃、损耗、补录历史库存 请求体建议: ```json { "batch_id": 12, "new_remaining_quantity": 3, "notes": "盘点修正" } ``` 如果不想新增单独接口,也可以让 `PUT /batches/:batchId` 承担这部分工作,但单独接口在语义上更清楚。 ### 11.4 与现有接口的兼容方案 当前前端详情页已经使用: - [frontend/src/pages/AssetDetailPage.vue#L1017-L1046](frontend/src/pages/AssetDetailPage.vue#L1017-L1046) - [backend/src/index.js#L1714-L1746](backend/src/index.js#L1714-L1746) 也就是一个很简单的 `consume` 流程: - 传 `amount` - 直接扣 `assets.remaining_quantity` 为了平滑升级,建议这样兼容: 1. 第一阶段先保留 `/api/assets/:id/consume` 路径不变 2. 内部实现从“直接扣主档”改为“写批次流水 + 重算主档” 3. 返回值仍然继续返回资产详情对象,前端可以先不大改 4. 等批次 UI 准备好后,再增加批次列表与批次编辑能力 这样可以避免一次性同时重写: - 后端路由 - 前端详情页 - 列表页库存展示 - 提醒中心逻辑 ### 11.5 提醒设计 #### 低库存提醒 仍沿用当前资产级提醒: - `type = low_stock` - `asset_id = 主物品` - `batch_id = null` #### 批次到期提醒 新增批次级提醒: - `type = expiry` - `asset_id = 主物品` - `batch_id = 对应批次` 提醒文案建议更具体,例如: - `纸巾 第 2 批次临近到期` - `阿莫西林(2026-05-01 批次)即将过期` ### 11.6 迁移方案 为了兼容当前已有的消耗品数据,建议做一次自动迁移。 #### 迁移规则 对于所有 `asset_type = consumable` 的资产: 1. 生成一条初始批次记录 2. 批次字段来源: - `purchase_date <- assets.purchase_date` - `quantity <- assets.quantity` - `remaining_quantity <- assets.remaining_quantity ?? assets.quantity` - `unit <- assets.unit` - `purchase_channel <- assets.purchase_channel` - `order_no <- assets.order_no` - `production_date <- assets.production_date` - `expiry_date <- assets.expiry_date` - `opened_at <- assets.opened_at` 3. 如果原资产没有 `purchase_date`,可退回 `created_at` 的日期部分 4. 迁移完成后,重新计算主档 `remaining_quantity` 和提醒 #### 迁移目标 - 老数据不丢 - 老页面不立刻失效 - 新模型可以从老模型平滑演进 ### 11.7 前端交互建议 #### 消耗品详情页 建议新增四个区域: 1. **库存总览** - 总剩余量 - 单位 - 低库存阈值 - 最近到期日 2. **采购批次** - 列表显示每一批数量、剩余、购买时间、到期时间 - 支持新增、编辑、手动调整 3. **消耗记录** - 展示每次消耗流水 4. **提醒记录** - 展示低库存和到期提醒 #### 新增采购批次入口 建议优先放在: - 消耗品详情页 - 或消耗品列表页的快捷操作 #### 主档创建页 主档创建时建议只保留: - 名称 - 小分类 - 单位 - 低库存阈值 - 备注 不建议在主档创建页继续要求用户填写完整批次信息;更自然的流程是: - 先建“纸巾” - 再新增第一条采购批次 ### 11.8 建议实施顺序 1. **数据库先落表**:`consumable_batches`、`consumable_transactions`、`reminders.batch_id` 2. **后端先兼容现有 consume 接口**:内部改为走批次扣减 3. **补批次 CRUD 接口**:新增/查询/编辑批次 4. **详情页补批次视图**:先做只读列表,再做新增与编辑 5. **最后再改主档表单**:把数量、保质期等“批次事实字段”从主档入口淡化 这样风险最小,也最符合当前代码结构。 ### 11.9 SQLite SQL 草案 下面是偏实现导向的 SQL 草案,目标是: - 不推翻现有 `assets` 表 - 新增批次与流水表 - 尽量兼容现有提醒体系 #### A. 新增批次表 ```sql CREATE TABLE IF NOT EXISTS consumable_batches ( id INTEGER PRIMARY KEY AUTOINCREMENT, asset_id INTEGER NOT NULL, user_id INTEGER NOT NULL DEFAULT 1, batch_code TEXT NOT NULL DEFAULT '', source_type TEXT NOT NULL DEFAULT 'purchase', purchase_date TEXT NOT NULL DEFAULT '', quantity REAL NOT NULL DEFAULT 0, remaining_quantity REAL NOT NULL DEFAULT 0, unit TEXT NOT NULL DEFAULT '', unit_price REAL DEFAULT NULL, total_price REAL DEFAULT NULL, purchase_channel TEXT NOT NULL DEFAULT '', order_no TEXT NOT NULL DEFAULT '', production_date TEXT NOT NULL DEFAULT '', expiry_date TEXT NOT NULL DEFAULT '', opened_at TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'active', notes TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY(asset_id) REFERENCES assets(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_consumable_batches_asset_purchase ON consumable_batches(asset_id, purchase_date ASC, id ASC); CREATE INDEX IF NOT EXISTS idx_consumable_batches_asset_expiry ON consumable_batches(asset_id, expiry_date ASC, id ASC); CREATE INDEX IF NOT EXISTS idx_consumable_batches_user_status ON consumable_batches(user_id, status); ``` #### B. 新增消耗流水表 ```sql CREATE TABLE IF NOT EXISTS consumable_transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, asset_id INTEGER NOT NULL, batch_id INTEGER, user_id INTEGER NOT NULL DEFAULT 1, type TEXT NOT NULL DEFAULT 'consume', amount REAL NOT NULL DEFAULT 0, unit TEXT NOT NULL DEFAULT '', consume_date TEXT NOT NULL DEFAULT '', mode TEXT NOT NULL DEFAULT 'auto', notes TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL, FOREIGN KEY(asset_id) REFERENCES assets(id) ON DELETE CASCADE, FOREIGN KEY(batch_id) REFERENCES consumable_batches(id) ON DELETE SET NULL ); CREATE INDEX IF NOT EXISTS idx_consumable_transactions_asset_date ON consumable_transactions(asset_id, consume_date DESC, id DESC); CREATE INDEX IF NOT EXISTS idx_consumable_transactions_batch_date ON consumable_transactions(batch_id, consume_date DESC, id DESC); ``` #### C. 对 reminders 表做兼容扩展 ```sql ALTER TABLE reminders ADD COLUMN batch_id INTEGER; CREATE INDEX IF NOT EXISTS idx_reminders_batch_id ON reminders(batch_id); ``` 说明: - SQLite 的 `ALTER TABLE` 能力有限,这种方式最稳妥。 - `batch_id` 允许为空,用于兼容现有资产级提醒。 #### D. 可选:对批次做唯一性约束(第二阶段再上) 如果后面发现同一订单号 + 同一主物品的批次重复录入较多,可再考虑: ```sql CREATE UNIQUE INDEX IF NOT EXISTS idx_consumable_batches_asset_order_batch ON consumable_batches(asset_id, order_no, batch_code); ``` 但第一版不建议太早收紧,避免历史数据或手工录入被卡住。 #### E. 现有资产字段的聚合更新 SQL 思路 每次批次变化后,建议统一重算主档: ```sql UPDATE assets SET remaining_quantity = ( SELECT COALESCE(SUM(remaining_quantity), 0) FROM consumable_batches WHERE asset_id = @asset_id AND status IN ('active', 'expired') ), expiry_date = ( SELECT COALESCE(MIN(expiry_date), '') FROM consumable_batches WHERE asset_id = @asset_id AND remaining_quantity > 0 AND expiry_date != '' AND status IN ('active', 'expired') ), updated_at = @updated_at WHERE id = @asset_id AND user_id = @user_id; ``` 然后再由后端根据总剩余量与阈值决定: - `库存正常` - `低库存` - `已耗尽` #### F. 旧数据迁移草案 把现有消耗品主档迁成首条批次时,可按下面思路批量生成: ```sql INSERT INTO consumable_batches ( asset_id, user_id, batch_code, source_type, purchase_date, quantity, remaining_quantity, unit, total_price, purchase_channel, order_no, production_date, expiry_date, opened_at, status, notes, created_at, updated_at ) SELECT id AS asset_id, user_id, '' AS batch_code, 'migration' AS source_type, COALESCE(NULLIF(purchase_date, ''), substr(created_at, 1, 10)) AS purchase_date, COALESCE(quantity, 0) AS quantity, COALESCE(remaining_quantity, quantity, 0) AS remaining_quantity, COALESCE(unit, '') AS unit, CASE WHEN purchase_price > 0 THEN purchase_price ELSE NULL END AS total_price, COALESCE(purchase_channel, '') AS purchase_channel, COALESCE(order_no, '') AS order_no, COALESCE(production_date, '') AS production_date, COALESCE(expiry_date, '') AS expiry_date, COALESCE(opened_at, '') AS opened_at, CASE WHEN COALESCE(remaining_quantity, quantity, 0) <= 0 THEN 'exhausted' ELSE 'active' END AS status, '从旧版消耗品主档迁移生成' AS notes, created_at, updated_at FROM assets WHERE asset_type = 'consumable'; ``` ### 11.10 接口请求/响应示例 以下示例以“纸巾”主物品为例,假设其 `asset_id = 101`。 #### A. 获取批次列表 ```http GET /api/assets/101/batches ``` 响应示例: ```json { "items": [ { "id": 12, "asset_id": 101, "batch_code": "", "source_type": "purchase", "purchase_date": "2026-05-01", "quantity": 10, "remaining_quantity": 2, "unit": "包", "unit_price": null, "total_price": 59.9, "purchase_channel": "京东", "order_no": "JD-001", "production_date": "2026-04-01", "expiry_date": "2027-05-01", "opened_at": "", "status": "active", "notes": "首批采购", "created_at": "2026-05-01T12:00:00.000Z", "updated_at": "2026-06-18T10:00:00.000Z" }, { "id": 13, "asset_id": 101, "batch_code": "", "source_type": "purchase", "purchase_date": "2026-06-10", "quantity": 6, "remaining_quantity": 6, "unit": "包", "unit_price": null, "total_price": 39.9, "purchase_channel": "淘宝", "order_no": "TB-002", "production_date": "2026-05-01", "expiry_date": "2027-06-10", "opened_at": "", "status": "active", "notes": "补货", "created_at": "2026-06-10T09:00:00.000Z", "updated_at": "2026-06-10T09:00:00.000Z" } ], "summary": { "asset_id": 101, "total_quantity": 16, "total_remaining_quantity": 8, "unit": "包", "low_stock_threshold": 4, "status": "库存正常", "next_expiry_date": "2027-05-01", "batch_count": 2, "active_batch_count": 2, "is_low_stock": false } } ``` #### B. 新增采购批次 ```http POST /api/assets/101/batches Content-Type: application/json ``` 请求示例: ```json { "purchase_date": "2026-06-10", "quantity": 6, "remaining_quantity": 6, "unit": "包", "total_price": 39.9, "purchase_channel": "淘宝", "order_no": "TB-002", "production_date": "2026-05-01", "expiry_date": "2027-06-10", "opened_at": "", "notes": "补货" } ``` 响应示例: ```json { "message": "批次创建成功", "batch": { "id": 13, "asset_id": 101, "purchase_date": "2026-06-10", "quantity": 6, "remaining_quantity": 6, "unit": "包", "total_price": 39.9, "purchase_channel": "淘宝", "order_no": "TB-002", "expiry_date": "2027-06-10", "status": "active" }, "summary": { "total_remaining_quantity": 8, "status": "库存正常", "next_expiry_date": "2027-05-01" } } ``` #### C. 编辑批次 ```http PUT /api/assets/101/batches/13 Content-Type: application/json ``` 请求示例: ```json { "remaining_quantity": 5, "expiry_date": "2027-06-20", "notes": "拆包后修正" } ``` 响应示例: ```json { "message": "批次更新成功", "batch": { "id": 13, "asset_id": 101, "remaining_quantity": 5, "expiry_date": "2027-06-20", "status": "active", "notes": "拆包后修正" }, "summary": { "total_remaining_quantity": 7, "status": "库存正常", "next_expiry_date": "2027-05-01" } } ``` #### D. 自动消耗库存(升级现有 consume) ```http POST /api/assets/101/consume Content-Type: application/json ``` 请求示例: ```json { "amount": 5, "mode": "auto", "consume_date": "2026-06-18", "notes": "家庭日常使用" } ``` 响应示例: ```json { "message": "消耗记录成功", "asset": { "id": 101, "name": "纸巾", "asset_type": "consumable", "remaining_quantity": 3, "unit": "包", "low_stock_threshold": 4, "status": "低库存", "expiry_date": "2027-06-10" }, "allocations": [ { "batch_id": 12, "consumed_amount": 2, "remaining_quantity_after": 0 }, { "batch_id": 13, "consumed_amount": 3, "remaining_quantity_after": 3 } ], "summary": { "total_remaining_quantity": 3, "status": "低库存", "is_low_stock": true, "next_expiry_date": "2027-06-10" } } ``` #### E. 手动指定批次消耗 ```http POST /api/assets/101/consume Content-Type: application/json ``` 请求示例: ```json { "amount": 2, "mode": "manual", "batch_id": 13, "consume_date": "2026-06-20", "notes": "优先消耗新开封的一批" } ``` 响应示例: ```json { "message": "消耗记录成功", "asset": { "id": 101, "remaining_quantity": 1, "status": "低库存" }, "allocations": [ { "batch_id": 13, "consumed_amount": 2, "remaining_quantity_after": 1 } ] } ``` #### F. 获取消耗流水 ```http GET /api/assets/101/consumptions ``` 响应示例: ```json { "items": [ { "id": 31, "asset_id": 101, "batch_id": 13, "type": "consume", "amount": 2, "unit": "包", "consume_date": "2026-06-20", "mode": "manual", "notes": "优先消耗新开封的一批", "created_at": "2026-06-20T08:30:00.000Z" }, { "id": 30, "asset_id": 101, "batch_id": 12, "type": "consume", "amount": 2, "unit": "包", "consume_date": "2026-06-18", "mode": "auto", "notes": "家庭日常使用", "created_at": "2026-06-18T10:00:00.000Z" } ] } ``` #### G. 手动库存调整 ```http POST /api/assets/101/stock-adjust Content-Type: application/json ``` 请求示例: ```json { "batch_id": 13, "new_remaining_quantity": 3, "notes": "盘点修正" } ``` 响应示例: ```json { "message": "库存调整成功", "batch": { "id": 13, "remaining_quantity": 3, "status": "active" }, "summary": { "total_remaining_quantity": 3, "status": "低库存", "is_low_stock": true } } ``` #### H. 常见错误响应建议 统一建议继续沿用当前风格: ```json { "message": "资产不存在" } ``` ```json { "message": "仅消耗品支持记录批次" } ``` ```json { "message": "批次不存在" } ``` ```json { "message": "消耗数量不能超过该批次剩余数量" } ``` ```json { "message": "总可用库存不足" } ``` ### 11.11 后端事务处理建议 实现时,下面几类操作建议全部包在 SQLite 事务里: 1. **新增批次** - 写 `consumable_batches` - 重算主档汇总 - 重建提醒 - 写操作日志 2. **消耗库存** - 查询可用批次 - 更新一个或多个批次的 `remaining_quantity` - 写一条或多条 `consumable_transactions` - 重算主档汇总 - 更新主档状态 - 重建提醒 - 写操作日志 3. **库存调整** - 更新批次 - 写调整流水 - 重算主档汇总 - 重建提醒 - 写操作日志 这样可以避免: - 批次扣了但主档没更新 - 主档更新了但提醒没重算 - 流水写了一半失败 ## 12. 当前实现特点与限制 ### 当前实现特点 - 后端逻辑集中,开发效率高,定位问题快 - 资产三类型已经打通列表、详情、提醒和统计,提醒中心和统计页提供紧凑筛选控制台 - 虚拟物品与消耗品状态改为系统自动维护:前端不再提供手动改状态入口,并统一用“续费状态 / 库存状态”语义展示 - 详情页继续强化为控制台式信息组织:虚拟物品突出续费总览、周期说明、最近账单信息、续费阶段提示和记录,消耗品突出库存总览、批次状态、库存提示、余量进度、手动新增批次和操作入口 - 元数据已按用户隔离 - 识别配置支持 OCR / OCR + LLM / Vision LLM 三种模式,并按资产类型把虚拟物品落到续费记录、消耗品落到采购批次 - 识别记录页展示资产类型、订单号、解析摘要、确认后的目标资产,支持按三类资产编辑确认字段 - 消耗品详情页的“识别新增批次”支持模式切换、单卡片识别流、状态徽标、摘要回填、重复订单号警示和原文展开,交互上向主识别流程对齐 ### 当前限制 - 后端还是单文件大模块,后续继续长大会增加维护成本 - 当前没有自动化测试套件 - 当前没有 lint 配置 - 消耗品的批次模型尚未落地 - 小分类尚未按主资产类型分组维护 ## 13. 文档维护约定 本 README 用来记录: - 系统定位 - 当前已实现能力 - 核心架构 - 关键业务模型 - 已确认的后续规划 后续每次新增重要功能时,建议同步更新 README,至少补充以下内容: 1. 功能用途 2. 影响的业务模型 3. 新增页面或接口 4. 是否属于已上线能力还是规划能力 5. 若涉及数据结构变更,补充迁移说明 ## 14. 参考文件 - [backend/src/index.js](backend/src/index.js) - [backend/src/db.js](backend/src/db.js) - [frontend/src/router.js](frontend/src/router.js) - [frontend/src/App.vue](frontend/src/App.vue) - [frontend/src/api.js](frontend/src/api.js) - [frontend/src/pages/AssetFormPage.vue](frontend/src/pages/AssetFormPage.vue) - [frontend/src/pages/AssetDetailPage.vue](frontend/src/pages/AssetDetailPage.vue) - [frontend/src/pages/SettingsPage.vue](frontend/src/pages/SettingsPage.vue) - [frontend/src/pages/PendingPurchasesPage.vue](frontend/src/pages/PendingPurchasesPage.vue) --- 如果你准备继续细化下一步,我建议接着补两份内容: 1. **消耗品批次模型的数据库与 API 设计** 2. **三类资产字段配置的统一设计文档**