Commit Graph

362 Commits

Author SHA1 Message Date
8553a46143 fix(poll): GetPackages 接口新增分页 POLL-03
- 新增 AssetPackagesResult DTO(含 total/page/page_size/items 字段)
- GetPackages 签名新增 page/pageSize 参数,默认 page=1、pageSize=50、最大 100
- admin/asset.go:Packages 从 Query 参数读取 page/page_size 传入 Service
- client_asset.go 调用处同步更新,使用默认分页参数
2026-03-28 10:41:20 +08:00
4be473c248 fix(realname): 移除 auto_purchase.go 中废弃的 EnableRealnameActivation 引用
- C 端充值触发的自动购包不需要等实名(前置检查已完成)
- 删除对已废弃 EnableRealnameActivation 字段的引用
2026-03-28 10:40:08 +08:00
8a531237c3 fix(poll): gatewayClient=nil 时停复机返回错误而非成功 POLL-02
- stopCardWithRetry: gatewayClient==nil 时返回业务错误而非 nil(静默跳过会假装停机成功)
- resumeCardWithRetry: 同上修复
2026-03-28 10:39:47 +08:00
922a3d0b49 fix(poll): polling:protect 接入调度器 POLL-01
- processSchedule 中新增 TaskTypePollingProtect 的 processManualQueue 和 processTimedQueue 调用
- 保护期一致性检查任务现在会被定期调度处理
2026-03-28 10:39:18 +08:00
60b43bb29e fix(realname): ActivateByRealname 按 ExpiryBase 选择计时基准 REALNAME-04
- from_purchase:用 PackageUsage.CreatedAt(购买时刻)作为激活时间起点
- from_activation(默认):用实名被触发的当前时刻作为激活时间起点
2026-03-28 10:39:00 +08:00
bdac4ab7b2 fix(fin): 激活配置并发行锁保护 FIN-03
- commission_withdrawal_setting/service.go: 激活事务内首步加 FOR UPDATE 行锁,防止并发时出现多条 is_active=true
- wechat_config_store.go: ActivateInTx 激活前同样加行锁保护
2026-03-28 10:38:53 +08:00
0ebb3d75f3 fix(realname): C 端实名检查改为按卡类型判断 REALNAME-03
- 删除 packagesNeedRealname 函数(依赖已废弃的 EnableRealnameActivation)
- 改为按 assetInfo.CardCategory==normal 判断是否需要实名
- 行业卡(industry)绕过实名检查,普通卡(normal)需要实名
2026-03-28 10:38:44 +08:00
7dd55630dc fix(realname): activateMainPackage 三维决策重构 REALNAME-02
- 替换 EnableRealnameActivation 布尔判断为三维决策逻辑
- 行业卡(card_category=industry)永远直接激活
- C 端购买(BuyerTypePersonal)前置实名检查已完成,直接激活
- 后台囤货按 ExpiryBase 决定:from_activation 等实名,from_purchase 立即激活
2026-03-28 10:38:24 +08:00
9ee2f0b466 fix(fin): 提现拒绝补 remark 必填校验 FIN-02
- CommissionWithdrawalHandler 新增 validator 字段
- RejectWithdrawal 在 BodyParser 后补充 validator.Struct 参数验证,确保 remark 必填
2026-03-28 10:37:54 +08:00
244a2742c1 fix(realname): DTO 更新 expiry_base 替换 enable_realname_activation REALNAME-05
- package_dto.go: CreatePackageRequest/UpdatePackageRequest/PackageResponse 三处替换
- service.go: 同步更新 DTO 映射逻辑使用 ExpiryBase
2026-03-28 10:37:51 +08:00
8f62496250 fix(fin): 补全提现审批人字段 FIN-01
- Approve updates map 补充 approved_by 和 approved_at 字段,确保审批人信息正确填充
2026-03-28 10:36:33 +08:00
344850fe1d fix(realname): DB 迁移 + Model + Store 清理 REALNAME-01
- 新增迁移 000089: 移除 enable_realname_activation,添加 expiry_base 字段
- package.go: 删除 EnableRealnameActivation 字段,新增 ExpiryBase 字段
- package_store.go: 移除两步写入特殊处理,改为直接一步 Create
2026-03-28 10:36:31 +08:00
2a92ab64af fix(perm): 修复企业账号资产权限边界 PERM-01/02
- PERM-01: 删除 Resolve 接口对企业账号的错误拦截(企业账号应可查询资产)
- PERM-02: 在 Refresh 接口开头新增企业账号拦截(企业账号只读,不允许主动触发运营商刷新)
2026-03-28 10:36:17 +08:00
62eb43a1cf docs(02-perm-fin-realname-poll): 创建 Phase 2 执行计划(2 个 Plan) 2026-03-28 10:29:50 +08:00
84aca2fd63 docs(02): capture phase context 2026-03-28 10:11:04 +08:00
c083596ef6 docs(phase-01): evolve PROJECT.md after phase completion — move CRITICAL-01~08 to Validated
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m18s
2026-03-28 09:49:15 +08:00
fb14c51fc2 docs(phase-01): complete phase execution — verification passed, all CRITICAL-01~08 satisfied 2026-03-28 09:47:54 +08:00
dbeed92ed9 docs(01-03): 完成充值回调自动购包链路修复 Plan 执行总结
- 创建 01-03-SUMMARY.md(CRITICAL-04 + CRITICAL-05 修复记录)
- 更新 STATE.md(Plan 计数推进至 4/5,progress 100%)
- 更新 ROADMAP.md(Phase 1 进度更新)
- 标记 CRITICAL-04、CRITICAL-05 需求已完成
2026-03-27 23:33:47 +08:00
030425be33 feat(01-03): AutoPurchaseHandler 注入 asynqClient,触发佣金并注册处理器(CRITICAL-04/05)
- AutoPurchaseHandler struct 新增 asynqClient *asynq.Client 字段
- NewAutoPurchaseHandler() 新增 asynqClient 参数
- ProcessTask 事务成功后入队 TaskTypeCommission(事务外,防止一致性问题)
- pkg/queue/handler.go 新增 registerAutoPurchaseHandler() 方法
- RegisterHandlers() 调用 registerAutoPurchaseHandler()
2026-03-27 23:31:49 +08:00
0ed804c4b3 docs(01-02): 完成 PollingHandler 注入 asynq.Client 修复断链计划摘要 2026-03-27 23:30:47 +08:00
20b4b6d6bf fix(01-02): 修复实名激活任务断链,为 PollingHandler 注入 asynq.Client
- PollingHandler struct 新增 asynqClient *asynq.Client 字段
- NewPollingHandler 构造函数新增 asynqClient 参数(redis 之后,gatewayClient 之前)
- triggerFirstRealnameActivation 改用 asynqClient.EnqueueContext,删除 RPush 降级方案
- 删除 _ = task 废弃代码
- registerPollingHandlers 传入 h.asynqClient 参数
2026-03-27 23:29:12 +08:00
9a4d87a0d4 feat(01-03): 充值回调触发自动购包任务入队(CRITICAL-04)
- recharge.Service struct 新增 queueClient *queue.Client 字段
- recharge.New() 新增 queueClient 参数
- HandlePaymentCallback 事务成功后,LinkedPackageIDs 非空时入队 TaskTypeAutoPurchaseAfterRecharge
- bootstrap/services.go 的 recharge.New() 传入 deps.QueueClient
2026-03-27 23:29:05 +08:00
0df992c98a docs(01-04): 完成 sellerCostPrice CRITICAL-06 修正计划摘要 2026-03-27 23:25:48 +08:00
f74b7da25f fix(01-04): 修正场景5代理代购下级的sellerCostPrice赋值
- 三处代理代购下级分支(wallet场景子场景2.2)均将 sellerCostPrice = buyerCostPrice 改为 sellerCostPrice = operatorCostPrice
- buyerCostPrice 是下级成本价,operatorCostPrice 是操作方(代理自己)的成本价
- 使用 buyerCostPrice 会导致佣金差额为 0,所有上级代理无法获得应有佣金
- 修复涉及三个函数(普通下单/后台下单/H5下单),行号分别约在 264/551/813
2026-03-27 23:24:16 +08:00
6de830fcc4 docs(01-01): 完成实名状态常量统一计划执行总结
- 创建 01-01-SUMMARY.md(CRITICAL-01 + CRITICAL-02 完成)
- STATE.md: 更新进度至 Plan 3/5,记录执行指标
- ROADMAP.md: 更新 Phase 1 计划进度(2/5 完成)
- REQUIREMENTS.md: 标记 CRITICAL-01、CRITICAL-02 已完成
2026-03-27 22:58:05 +08:00
da50a35a62 fix(01-01): 修复 C 端实名校验使用常量(CRITICAL-02)
- client_order/service.go: 实名校验 assetInfo.RealNameStatus != 1 改为常量比较
- client_realname.go: 实名状态检查 == 1 改为 constants.RealNameStatusVerified
- go build ./... 编译通过,无遗留硬编码 != 1 / == 1 的实名判断
2026-03-27 22:54:50 +08:00
1fd0072f69 fix(01-01): 统一实名状态常量(CRITICAL-01 核心)
- polling_handler.go: parseRealnameStatus 不再返回硬编码 2,改为 constants.RealNameStatusVerified(=1)
- polling_handler.go: isFirstRealname 判断改用常量比较,不再使用 == 2
- polling_handler.go: HandleProtectConsistencyCheck 注释更正为 real_name_status=1
- iot_card.go: RealNameStatus gorm comment 统一为 0-未实名 1-已实名(去除行业卡说明)
- asset_dto.go: RealNameStatus description 更正为 0未实名 1已实名(去除错误的 1实名中 2已实名)
- scheduler.go: getCardCondition 改用常量替代硬编码 0/1/2 判断
- iot_card/service.go: parseGatewayRealnameStatus 注释更正,返回值改为常量
2026-03-27 22:53:30 +08:00
bfcea1c18f docs(01-05): 完成 Plan 05 执行摘要,更新 STATE/ROADMAP/REQUIREMENTS
- 创建 01-05-SUMMARY.md(CRITICAL-07 + CRITICAL-08 修复记录)
- STATE.md 进度推进至 plan 2/5(20%)
- ROADMAP.md 更新 Phase 1 进度(1/5 SUMMARY 已完成)
- REQUIREMENTS.md 标记 CRITICAL-07、CRITICAL-08 为完成
2026-03-27 22:52:27 +08:00
db166807c7 fix(01-05): 提现冻结并发校验缺失(CRITICAL-08)
- 冻结余额改为 result := tx...Updates(),分离结果对象
- 检查 result.Error 处理数据库错误
- 检查 result.RowsAffected == 0 防止并发余额不足时仍创建提现单
- RowsAffected == 0 时返回 errors.New(CodeInsufficientBalance, ...)
2026-03-27 22:50:18 +08:00
809cb2b8a8 fix(01-05): 设备导入补充 IMEI 字段(CRITICAL-07)
- DeviceRow struct 新增 IMEI string 字段
- buildDeviceColumnIndex 支持 imei/设备imei/imei号 列名映射
- ParseDeviceExcel 解析数据行时填充 row.IMEI
- device_import.go processBatch 创建 model.Device 时赋值 IMEI: row.IMEI
2026-03-27 22:49:40 +08:00
f48f11beb4 docs(01-p0): 创建 Phase 1 P0 紧急修复执行计划(5 个 Plan,涵盖 CRITICAL-01~08) 2026-03-27 22:35:28 +08:00
5862018e7c docs(01): capture phase context 2026-03-27 21:52:09 +08:00
dc9bf0c9b7 docs: create roadmap (6 phases) 2026-03-27 20:18:06 +08:00
7f2e978288 docs: define v1 requirements 2026-03-27 19:07:58 +08:00
3078bc71c8 docs: add research findings 2026-03-27 19:04:17 +08:00
cf59380784 docs: initialize project 2026-03-27 18:50:18 +08:00
876151ec1c 修复
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m22s
2026-03-23 09:49:30 +08:00
c10b70757f fix: 资产信息接口 device_realtime 字段返回固定假数据,避免前端因 nil 报错
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m58s
Gateway 同步接口尚未对接,临时为设备类型资产返回 mock 数据,
后续对接后搜索 buildMockDeviceRealtime 替换为真实数据
2026-03-21 14:42:48 +08:00
4d1e714366 fix: 补齐迁移 000076 遗漏的列名重命名(card_wallet_id → asset_wallet_id)
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m52s
迁移 000076 只将表名从 card_wallet 改为 asset_wallet,但遗漏了表内
card_wallet_id 列的重命名,导致 Model 中 column:asset_wallet_id 与数据库
实际列名不匹配,所有涉及该字段的 INSERT/SELECT 均报错 2002。

影响范围:
- tb_asset_recharge_record.card_wallet_id → asset_wallet_id
- tb_asset_wallet_transaction.card_wallet_id → asset_wallet_id
2026-03-21 14:30:29 +08:00
d2b765327c 完整的字段返回
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m52s
2026-03-21 13:41:44 +08:00
7dfcf41b41 fix: 修复卡类型资产绑定键错误导致归属校验永远失败
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m48s
resolveAssetBindingKey 对卡类型错误地返回 card.ICCID 作为绑定键,
但归属校验 isCustomerOwnAsset 使用 card.VirtualNo 比对,二者不一致
导致所有卡资产的 C 端接口返回 403 无权限。

修复:卡类型绑定键改为 card.VirtualNo,与设计文档一致。
附带数据迁移修正已有的错误绑定记录。
2026-03-21 11:33:57 +08:00
ed334b946b refactor: 清理重构遗留的死代码
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
- personal_customer.Service: 删除已迁移到 client_auth 的死方法
  (GetProfile/SendVerificationCode/VerifyCode),移除多余的
  verificationService/jwtManager 依赖
- 删除 internal/service/customer/ 整个目录(零引用的早期残留)
2026-03-21 11:33:06 +08:00
95b2334658 feat: 资产套餐历史接口新增 package_type 和 status 筛选条件
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m10s
GET /api/c/v1/asset/package-history 支持可选参数:
- package_type: formal(正式套餐) / addon(加油包)
- status: 0(待生效) / 1(生效中) / 2(已用完) / 3(已过期) / 4(已失效)
不传则返回全部,保持向后兼容。
2026-03-21 11:01:21 +08:00
da66e673fe feat: 接入短信服务,修复 SMS 客户端 API 路径
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
- cmd/api/main.go: 新增 initSMS() 初始化短信客户端并注入 verificationService
- pkg/sms/client.go: 修复 API 路径缺少 /sms 前缀(/api/... → /sms/api/...)
- docker-compose.prod.yml: 添加线上短信服务环境变量
2026-03-21 10:51:43 +08:00
284f6c15c7 fix: 修复个人客户设备绑定查询使用已废弃的 device_no 列名
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m4s
数据库列已重命名为 virtual_no,但 Store 层 3 处原始 SQL 仍使用旧列名 device_no,
导致小程序登录时查询客户资产绑定关系报 column device_no does not exist。
2026-03-20 18:20:24 +08:00
55918a0b88 fix: 修复 C 端公开路由被认证中间件拦截的问题
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m51s
Fiber 的 Group.Use() 在路由表中注册全局 USE 处理器,不区分 Group 对象。
原代码先调用 authProtectedGroup.Use() 再注册公开路由,导致 verify-asset、
wechat-login、miniapp-login、send-code 四个无需认证的接口被拦截返回 1004。

修复方式:公开路由直接注册在 router 上且在任何 Use() 之前,
利用 Fiber 按注册顺序匹配的机制确保公开路由优先命中。
2026-03-20 18:01:12 +08:00
d2494798aa fix: 修正停复机接口错误码,网关失败不再返回模糊的内部服务器错误
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m13s
- 单卡停复机:网关错误从 CodeInternalError(2001) 改为 CodeGatewayError(1110),前端可看到具体失败原因
- 单卡停复机:DB 更新裸返 GORM error 改为 CodeDatabaseError(2002) 包装
- 设备复机:全部卡失败时错误码从 CodeInternalError 改为 CodeGatewayError
2026-03-19 18:37:03 +08:00
b9733c4913 fix: 修正零售价架构错误 + 清理旧微信配置 + 归档提案 + 前端接口文档
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m12s
1. 修正 retail_price 架构:
   - 删除 batch-pricing 接口的 pricing_target 字段和 retail_price 分支
     (上级只能改下级成本价,不能改零售价)
   - 新增 PATCH /api/admin/packages/:id/retail-price 接口
     (代理自己改自己的零售价,校验 retail_price >= cost_price)

2. 清理旧微信 YAML 配置(已全部迁移到数据库 tb_wechat_config):
   - 删除 config.yaml 中 wechat.official_account 配置节
   - 删除 NewOfficialAccountApp() 旧工厂函数
   - 清理 personal_customer service 中的死代码(旧登录/绑定微信方法)
   - 清理 docker-compose.prod.yml 中旧微信环境变量和证书挂载注释

3. 归档四个已完成提案到 openspec/changes/archive/

4. 新增前端接口变更说明文档(docs/前端接口变更说明.md)

5. 修正归档提案和 specs 中关于 pricing_target 的错误描述
2026-03-19 17:39:43 +08:00
9bd55a1695 feat: 实现客户端核心业务接口(client-core-business-api)
新增客户端资产、钱包、订单、实名、设备管理等核心业务 Handler 与 DTO:
- 客户端资产信息查询、套餐列表、套餐历史、资产刷新
- 客户端钱包详情、流水、充值校验、充值订单、充值记录
- 客户端订单创建、列表、详情
- 客户端实名认证链接获取
- 客户端设备卡列表、重启、恢复出厂、WiFi配置、切卡
- 客户端订单服务(含微信/支付宝支付流程)
- 强充自动代购异步任务处理
- 数据库迁移 000084:充值记录增加自动代购状态字段
2026-03-19 13:28:04 +08:00
e78f5794b9 feat: 实现客户端换货系统(client-exchange-system)
新增完整换货生命周期管理:后台发起 → 客户端填收货信息 → 后台发货 → 确认完成(含可选全量迁移) → 旧资产转新再销售

后台接口(7个):
- POST /api/admin/exchanges(发起换货)
- GET /api/admin/exchanges(换货列表)
- GET /api/admin/exchanges/:id(换货详情)
- POST /api/admin/exchanges/:id/ship(发货)
- POST /api/admin/exchanges/:id/complete(确认完成+可选迁移)
- POST /api/admin/exchanges/:id/cancel(取消)
- POST /api/admin/exchanges/:id/renew(旧资产转新)

客户端接口(2个):
- GET /api/c/v1/exchange/pending(查询换货通知)
- POST /api/c/v1/exchange/:id/shipping-info(填写收货信息)

核心能力:
- ExchangeOrder 模型与状态机(1待填写→2待发货→3已发货→4已完成,1/2可取消→5)
- 全量迁移事务(11张表:钱包、套餐、标签、客户绑定等)
- 旧资产转新(generation+1、状态重置、新钱包、历史隔离)
- 旧 CardReplacementRecord 表改名为 legacy,is_replaced 过滤改为查新表
- 数据库迁移:000085 新建 tb_exchange_order,000086 旧表改名
2026-03-19 13:26:54 +08:00