Commit Graph

959 Commits

Author SHA1 Message Date
hailin a55201b3b3 feat(referral+mobile): 功能6 — App 团队预种数量展示
纯新增实现,不修改任何现有业务逻辑,对现有系统零风险。

## 后端 — referral-service

新建 TeamPrePlantingController(JWT 认证),2 个公开端点:

1. GET /referral/me/team-pre-planting
   - 返回当前用户的个人预种份数、团队预种总量
   - 返回直推成员列表及每人的预种份数
   - 从 TeamStatistics 表读取(CDC 事件维护的数据)

2. GET /referral/me/team-pre-planting/members?limit=20&offset=0
   - 分页返回全部团队成员的预种明细(仅有预种份数 > 0 的成员)
   - 使用 ancestor_path 数组查询所有下级用户
   - JOIN team_statistics 获取每人的 selfPrePlantingPortions

Kong 网关无需修改(/api/v1/referral/* 已覆盖)。

## 前端 — Flutter mobile-app

新建 TeamPrePlantingPage 页面:
- 顶部统计卡片:个人预种 + 团队预种总量
- 直推预种明细列表(所有用户可见)
- 全部团队成员预种明细(仅市/省公司管理者可见,分页加载更多)
- 普通用户看到锁定提示"仅市公司/省公司管理者可查看"

入口:个人中心页预种按钮行新增绿色「团队预种」按钮。

## 文件清单

新建文件:
- backend/.../controllers/team-pre-planting.controller.ts(核心后端控制器)
- frontend/.../pages/team_pre_planting_page.dart(Flutter 团队预种页面)

微量修改(仅追加新行):
- controllers/index.ts: +1 行 export
- api.module.ts: +2 行 import/注册
- api_endpoints.dart: +2 行端点常量
- referral_service.dart: +4 模型类 +2 API 方法
- route_paths.dart, route_names.dart: +1 行路由定义
- app_router.dart: +1 import +1 GoRoute
- profile_page.dart: 预种按钮行追加第三个按钮

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 07:15:17 -08:00
hailin 5fad40cec1 fix(mining-app): 修复记录页面时间显示为UTC而非北京时间的问题
所有记录页面的 DateFormat().format() 缺少 .toLocal() 调用,
导致后端返回的 UTC 时间直接显示,与北京时间相差8小时。

修复范围(5个页面,10处):
- mining_records_page: record.createdAt
- batch_mining_records_page: record.createdAt
- contribution_records_page: record.effectiveDate, record.expireDate
- planting_records_page: summary.firstPlantingAt, summary.lastPlantingAt,
  record.adoptionDate, record.createdAt
- trading_records_page: order.createdAt, trade.createdAt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 06:12:16 -08:00
hailin 6e3a898801 fix(admin-web): 系统账户非标准区域代码显示为"测试省/测试市"
调试期间产生的短位数区域代码(如 44、4401)无法匹配标准
6位行政区划码,之前直接显示原始数字。现改为:
- 2位数字 → 测试省 (XX)
- 4位数字 → 测试市 (XXXX)
- 其他未识别纯数字 → 测试账户 (XXX)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 05:56:41 -08:00
hailin 1bfeece109 fix(admin-web): 仪表板预种统计改为方块卡片样式
- 预种待合并/已合并从深色条形卡片改为与认种统计一致的 StatCard
- 统计网格从 4 列调整为 3 列(3+3 布局更工整)
- 骨架屏占位从 4 个调整为 6 个

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 05:24:00 -08:00
hailin 19753a8639 feat(admin-web): 仪表板添加预种待合并/已合并统计卡片
在网络因子/加速因子下方新增一行:
- 预种待合并: 显示全网 pendingMergePortions(份)
- 预种已合并: 显示全网 totalTreesMerged(棵)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 05:19:03 -08:00
hailin 7fff665d9b fix(admin-web): PrePlantingStats 类型添加 pendingMergePortions 字段
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 05:04:57 -08:00
hailin 17df9b6df1 feat(admin): 预种管理页添加"待合并总量"统计项
- planting-service stats 端点新增 pendingMergePortions 字段
  从 PrePlantingPosition 表聚合 SUM(available_portions)
- admin-web 统计卡片区新增"待合并总量(份)"显示
- 网格布局从 4 列调整为 5 列

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 04:17:38 -08:00
hailin b3f3349190 fix(admin-web): 预种管理页更新价格与单位
- 每份价格: 3566 USDT → 1887 绿积分(匹配 10份合1树 方案)
- 金额单位: USDT → 绿积分(状态栏、统计卡片、订单表头)
- 协议 placeholder: 1/5份额 8% → 1/10份额 4%

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 04:07:28 -08:00
hailin ac15d6682a feat(admin): 用户预种数量展示 & 授权申请照片查看
## 功能7:用户管理展示预种数量

### 需求
在用户管理(列表+详情页)展示个人及团队预种份数,
并支持跳转预种管理页查看团队中哪些ID购买了预种。

### 后端变更

referral-service — 新增内部 API:
  - GET  /internal/referral/pre-planting-stats/:accountSequence
  - POST /internal/referral/pre-planting-stats/batch
  从 TeamStatistics 表查询 selfPrePlantingPortions/teamPrePlantingPortions

admin-service:
  - 新建 ReferralProxyService (src/referral/) 代理 referral-service 内部 API
  - UserFullDetailDto 新增 selfPrePlantingPortions, teamPrePlantingPortions
  - user-detail.controller 并行调用获取预种统计
  - user.controller 批量获取用户列表预种统计
  - UserListItemDto 新增 selfPrePlantingPortions, teamPrePlantingPortions
  - pre-planting-config.controller 新增 teamOf 查询参数
    → 先从 referralQueryView 获取团队成员列表
    → 将 accountSequences 传递给 planting-service 过滤

planting-service:
  - internal-pre-planting.controller 的 admin orders/positions 端点
    新增 accountSequences 查询参数,支持按用户列表过滤

### 前端变更

admin-web:
  - 用户列表页表格新增"个人预种(份)"、"团队预种(份)"两列
  - 用户详情页新增预种统计卡片(个人/团队预种份数)
  - 团队预种数字可点击,跳转 /pre-planting?teamOf={accountSequence}
  - 预种管理页支持 teamOf URL 参数,团队过滤模式下显示提示条+返回链接

---

## 功能8:授权管理查看申请照片

### 需求
管理后台无法查看社区/市公司/省公司申请时提供的办公室照片。
原因:authorization-service 收到 officePhotoUrls 后只验证未持久化。
照片文件本身已存储在 MinIO,只需持久化 URL 到数据库。

### 后端变更

authorization-service:
  - schema.prisma: AuthorizationRole 新增 officePhotoUrls String[] 字段
  - authorization-role.aggregate: props/字段/getter/构造/toPersistence/所有工厂方法
  - repository: save() create/update 块 + toDomain() 映射
  - application service: 3个 self-apply 方法传递 officePhotoUrls 到 aggregate
  - admin controller: queryAuthorizations 响应包含 officePhotoUrls

admin-service:
  - schema.prisma: AuthorizationRoleQueryView 新增 officePhotoUrls 字段
  - user-detail-query.repository 接口 + impl: 包含 officePhotoUrls
  - user-detail.dto: AuthorizationRoleDto 新增 officePhotoUrls

### 前端变更

admin-web:
  - 授权管理页表格新增"申请照片"列,点击弹出照片网格 Modal
  - 照片支持点击放大(全屏 lightbox 覆盖层)
  - 用户详情页授权信息 Tab 同样支持查看照片
  - authorization.types.ts / userDetail.types.ts 类型更新

---

## 部署注意事项
1. authorization-service 需运行: npx prisma migrate dev --name add-office-photo-urls
2. admin-service 需运行: npx prisma migrate dev --name add-auth-role-office-photos
3. 需部署: authorization-service, admin-service, referral-service, planting-service, admin-web

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 03:09:17 -08:00
hailin d5d61f4f68 fix(transfer): 修复转让记录 API 路径与参数不匹配
- API 路径: /transfers/my → /transfers(后端无 /my 子路由)
- 分页参数: page/pageSize → limit/offset(匹配后端 DTO)
- 转让记录页显示具体错误信息便于调试

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 23:35:09 -08:00
hailin 6efa74aded refactor(transfer): 移除发起转让页的售价字段 — 线下柜台签合同交易
APP 端树转让仅完成系统层面的所有权变更 + 算力调整(撤回旧用户算力、
新增至新用户及其团队),价格由用户线下到柜台签署合同确定。

变更详情:
- transfer_initiate_page.dart:
  · 移除 _priceController、_feeRate、费用相关 getter
  · 移除"每棵售价"输入框和费用计算卡片 UI
  · 移除 _feeRow 辅助方法
  · 更新说明文案:强调线下柜台签合同
  · 更新确认弹窗:仅显示买方账号和转让棵数
  · API 调用改用 treeCount 替代 pricePerTree
- transfer_service.dart:
  · createTransfer() 参数从 pricePerTree(double) 改为 treeCount(int)
  · 请求体字段同步调整

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:06:31 -08:00
hailin 0e34896d0b refactor(mobile-app): 树转让入口从"我"页面迁移至"实名认证"页面
将"发起转让"和"转让记录"按钮从 ProfilePage 顶部移除,
归类为"树服务"放入 KycEntryPage(实名认证入口页),
位于"其他操作 > 验证/更换手机号"的下方。

变更详情:
- profile_page.dart:
  · 移除 _buildTransferButtons() 方法及其调用
  · 移除 _goToTransferInitiate() / _goToTransferList() 导航方法
- kyc_entry_page.dart:
  · 在"验证/更换手机号"下方新增"树服务"分类标题
  · 新增"发起转让"操作卡片 → /transfer/initiate
  · 新增"转让记录"操作卡片 → /transfer/list
  · 复用页面已有的 _buildActionCard 样式,保持 UI 一致

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:47:39 -08:00
hailin b3984c861c feat(pre-planting): 预种方案调整 — 18870 USDT/棵,10份合1树
=== 方案变更 ===
- 全树基础价: 17830 → 18870 USDT
- 每份价格:   3566 → 1887 USDT
- 合并阈值:   5 份 → 10 份

=== 后端改动 (planting-service) ===
1. pre-planting-right-amounts.ts:
   - PRE_PLANTING_PRICE_PER_PORTION: 3566 → 1887
   - PRE_PLANTING_PORTIONS_PER_TREE: 5 → 10
   - 10类权益金额按 floor(整棵树/10) 重算,余数归 HQ_BASE_FEE(319)
2. pre-planting-merge.aggregate.ts:
   - 合并校验从硬编码 5 改为引用 PRE_PLANTING_PORTIONS_PER_TREE 常量
3. purchase-pre-planting.dto.ts:
   - portionCount @Max(5) → @Max(10)
4. pre-planting-application.service.ts:
   - 加价补贴计算 /5 → /PRE_PLANTING_PORTIONS_PER_TREE
   - 错误文案引用常量,消除硬编码

=== 前端改动 (mobile-app) ===
1. pre_planting_purchase_page.dart: 默认价格、份数、协议文本(1/10、4%)
2. pre_planting_position_page.dart: _portionsPerTree 5→10
3. pre_planting_merge_detail_page.dart: 总价值计算和单份显示金额
4. tree_pricing_service.dart: fallback 默认值
5. pre_planting_service.dart: JSON 解析 fallback 默认值
6. 各文件注释同步更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 18:32:32 -08:00
hailin fda79304c6 Revert "fix(ledger): 分享收益筛选支持多类型(REWARD_TO_SETTLEABLE + REWARD_PENDING)"
This reverts commit d223671db7.
2026-03-01 11:07:19 -08:00
hailin d223671db7 fix(ledger): 分享收益筛选支持多类型(REWARD_TO_SETTLEABLE + REWARD_PENDING)
后端 entryType 筛选支持逗号分隔多值,前端"分享收益"同时查询两种类型,
解决未认种时收到的 REWARD_PENDING 分享权益在筛选中丢失的问题。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:02:08 -08:00
hailin e4a2a0e37a feat(ledger): 流水明细显示来源用户ID + 统计兼容历史批量转换数据
- wallet_service.dart: LedgerEntry 新增 sourceAccountFromMemo 从 memo 提取来源用户
- ledger_detail_page.dart: 流水列表项显示"来自 Dxxx"金色文字
- ledger_detail_page.dart: 权益详情弹窗添加来源用户行和备注行
- wallet-application.service.ts: 统计/趋势保留 memo 兼容历史批量转换记录

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 10:41:11 -08:00
hailin d876dd1591 fix(wallet): 区分直接入账和批量转换的 REWARD_TO_SETTLEABLE
新增 REWARD_PENDING_CONVERTED 类型用于批量转换(待领取→可结算),
REWARD_TO_SETTLEABLE 保留给直接入账(hasPlanted=true时的新收入)。

统计排除:REWARD_PENDING_CONVERTED + REWARD_SETTLED(状态转换)
统计计入:REWARD_PENDING + REWARD_TO_SETTLEABLE(首次入账)

已迁移7条历史数据的 entry_type。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 10:30:47 -08:00
hailin 31e6f9e15a fix(mobile): 预种可结算列表以wallet实际金额为准,避免已结算条目残留
planting-service 的分配记录不跟踪 wallet 端的结算状态,
原来的反向排除法(排除PENDING+EXPIRED)无法处理:
1. 已结算到余额(SETTLED→余额)的条目
2. hasPlanted=true 后直接进可结算、不经 pending_rewards 的条目

改为以 walletInfo.rewards.settleableUsdt 为权威来源:
- settleableUsdt=0 时直接跳过(已全部结算到余额)
- settleableUsdt>0 时按金额截断,确保展示总额不超过实际可结算

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 10:19:31 -08:00
hailin 8e52535dd9 feat(mobile): 预种明细显示来源用户ID + 修复空待领取倒计时
1. 预种待领取/可结算明细的 memo 中追加来源用户(如"来自D26022600016的预种")
2. 修复 pendingUsdt=0 时倒计时仍然显示的问题(pending_expire_at 未清除时兜底)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 09:48:34 -08:00
hailin b13d873f64 feat(mobile): 流水明细中 REWARD_PENDING 标注"已转可结算"
用户购买预种后 hasPlanted=true,所有 PENDING 奖励转为 SETTLED,
此时流水中历史的 REWARD_PENDING 条目追加"(已转可结算)"标注,
避免用户误以为还有未领取的奖励。仅在 pendingUsdt=0 时显示。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 09:15:18 -08:00
hailin e9b9896317 fix(mobile): 修复预种可结算列表与金额不一致
问题:pre-planting/my-rewards 返回所有分配记录不区分状态,
导致 PENDING 状态的预种奖励也被错误地显示在可结算列表中,
而可结算金额(从 wallet-service 取值)正确为 0,造成列表和金额不一致。

修复:在合并预种可结算列表时,排除正在 PENDING 和 EXPIRED 状态的条目。
通过 wallet/pending-rewards 和 wallet/expired-rewards 获取实际状态,
用 sourceOrderId 交叉比对,只保留真正可结算的预种条目。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 08:24:03 -08:00
hailin 4996c1d110 feat(mobile): profile页待领取/可结算/已过期列表统一显示预种数据
变更概要:
- wallet_service.dart: 新增 WalletPendingRewardItem 模型和 getWalletPendingRewards() 方法
  调用 GET /wallet/pending-rewards 获取 wallet-service 的待领取奖励列表
- profile_page.dart: 合并预种待领取奖励到列表中
  从 wallet-service 待领取列表中筛选 PPL 前缀的预种条目,转换为 PendingRewardItem
  与 reward-service 的正常认种待领取统一展示
- profile_page.dart: 已过期列表标记预种条目
  wallet-service GET /wallet/expired-rewards 已包含预种过期记录,
  渲染时通过 sourceOrderId.startsWith('PPL') 动态添加 [预种] 前缀
- profile_page.dart: 所有汇总金额统一从 wallet-service 取值
  _pendingUsdt / _expiredUsdt / _remainingSeconds 改为从 walletInfo.rewards 读取,
  wallet_accounts 包含正常认种 + 预种,是唯一的 source of truth

技术说明:
- 后端零改动,仅前端变更(零风险)
- 预种条目通过订单号 PPL 前缀与正常认种区分,避免重复显示
- 所有预种条目在卡片上显示 [预种] 前缀,方便用户区分来源

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 07:52:02 -08:00
hailin 27cd72fe01 feat(pre-planting): 预种可结算收益结算 + 前端可结算金额修正
背景:
  预种奖励通过 planting-service → wallet-service allocateFunds 链路
  直接写入 wallet_accounts.settleable_usdt,不经过 reward-service。
  因此 reward-service 的一键结算(settleToBalance)无法覆盖预种部分,
  且 reward-service 的 summary.settleableUsdt 不包含预种金额。

改动:
1. wallet-service 新增 POST /wallet/settle-pre-planting 端点
   - 将 wallet 中剩余的 settleable_usdt 转入 available 余额
   - settleable_usdt=0 时幂等跳过,不创建空流水
   - 流水备注标注 [预种],payloadJson.source='pre-planting'

2. mobile-app 兑换页(trading_page):
   - 可结算金额改为从 wallet-service 的 wallet.rewards.settleableUsdt 取值
     (包含正常认种 + 预种的可结算部分,是唯一的 source of truth)
   - 一键结算流程改为两步串行:
     先调 reward-service settleToBalance(正常认种,不动现有逻辑),
     再调 wallet-service settle-pre-planting(预种部分,纯增量)

3. mobile-app 我的页(profile_page):
   - 并行加载新增 walletService.getMyWallet() 调用
   - _settleableUsdt 改为从 wallet.rewards.settleableUsdt 取值

不涉及的系统:
  - reward-service:零改动
  - planting-service:零改动
  - wallet-service 现有结算逻辑:零改动
  - admin-web:零改动

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 07:33:53 -08:00
hailin 05e590ef04 fix(pre-planting): 修复可结算收益重复计算
pre-planting getMyRewards API 错误地将所有分配记录金额算作
settleableUsdt(包括 PENDING 状态的待领取奖励)。
预种奖励的 PENDING/SETTLEABLE 状态由 wallet-service 管理,
reward-service 的 getMyRewardSummary 已包含预种可结算部分,
不应重复累加。

修复:
- 后端 getMyRewards 返回 settleableUsdt: 0
- 前端"我"页面和"兑换"页面不再额外加预种 settleableUsdt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:34:22 -08:00
hailin d1be7173be fix(admin-web): 引荐树节点点击导航到用户详情页
setTreeRootUser 只更新树不更新页面顶部信息,导致数据不同步。
改为 router.push 导航,页面整体刷新保持一致。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:13:42 -08:00
hailin cf07712a8c fix(mobile): 兑换页可结算收益聚合正常认种+预种金额
之前只显示正常认种的 settleableUsdt,未包含预种收益,
导致有预种收益但显示"暂无可结算收益"。
现在并行获取两者并累加,与"我"页面保持一致。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:11:13 -08:00
hailin 5392c47e47 fix(admin-web): 引荐关系树节点点击切换为该节点为根显示
点击节点后以该节点为根重新加载树,显示其祖先链和一级下线。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:00:45 -08:00
hailin e715fd2504 fix(admin-web): 引荐关系树节点点击跳转到用户详情页
之前点击只切换树根节点,不导航。改为 router.push 跳转。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 02:55:56 -08:00
hailin 292c6518ba fix(admin-web): 隐藏钱包流水表格的类型和关联订单列
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 02:49:59 -08:00
hailin 0ac131a3b7 fix(admin-web): 钱包流水表格备注列显示不全
各列均为 flex:1 等宽导致备注内容被 ellipsis 截断,
调整各列 flex 比例并允许备注列换行显示完整文本。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 02:41:31 -08:00
hailin 3be7b47678 feat(pre-planting+mobile): 预种奖励在"我"页面展示
## 问题
预种奖励直接走 planting-service → wallet-service,绕过 reward-service,
导致前端"我"页面读 reward-service 的 summary/settleable 数据时看不到预种奖励。

## 方案
前端同时读 reward-service(正常认种)和 planting-service(预种),合并展示。

## 后端(planting-service)
- PrePlantingRewardEntryRepository: 新增 findByRecipientAccountSequence() 方法,
  按收款方账户查询预种奖励记录(注入 PrismaService 替代事务 client)
- PrePlantingController: 新增 GET /pre-planting/my-rewards 端点,
  返回当前用户作为收款方收到的预种奖励汇总+明细列表
  格式与 reward-service 的 settleable 对齐(id, rightType, usdtAmount, sourceOrderNo 等)

## 前端(Flutter mobile-app)
- PrePlantingService: 新增 getMyRewards() 方法 + PrePlantingMyRewards/PrePlantingRewardItem 数据类
- profile_page.dart: 并行调用 prePlantingService.getMyRewards(),
  将预种奖励转为 SettleableRewardItem 合并到可结算列表,
  summary.settleableUsdt 也加上预种金额

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:03:53 -08:00
hailin 6a659ca718 feat(admin-web): 省/市区域账户添加点击查看明细功能
RegionAccountsSection 新增:
- 每行添加"查看明细"按钮,点击展开该账户分类账流水
- 明细表包含 时间/类型/金额/余额/来源账户/来源备注/备注 7列
- 复用 getAllLedger API 的 provinceAccountsLedger/cityAccountsLedger 数据
- 行点击和按钮点击均可展开/收起
- 新增 clickableRow/selectedRow CSS 样式

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 20:37:12 -08:00
hailin bf50810830 feat(wallet+admin-web): 系统账户流水增加来源用户账户和来源备注列
问题:系统账户(S0000000001等)、省/市区域/团队账户的流水明细
只显示 allocationType 英文标识,无法追溯是哪个用户的认种产生的。

解决方案:从 wallet_ledger_entries.payload_json.metadata 中提取
sourceAccountSequence 和 memo 字段,通过 API 返回给前端展示。

后端 wallet-service 改动:
- LedgerEntryDTO 新增 sourceAccountSequence / sourceMemo 两个可选字段
- 新增 extractPayloadInfo() 辅助函数统一从 payloadJson 提取信息
- 替换所有 5 处 LedgerEntryDTO 映射,使用 extractPayloadInfo()
- 向后兼容:旧记录无 metadata 时返回 null,不影响已有功能

前端 admin-web 改动:
- LedgerEntryDTO 类型新增 sourceAccountSequence / sourceMemo 字段
- 固定账户明细表格和分类账明细表格增加"来源账户"和"来源备注"列
- 新增 .sourceAccount 样式(等宽字体显示账户序列号)

数据来源说明:
- 正常认种:reward-service 传入 metadata 含完整中文 memo 和 sourceAccountSequence
- 预种:planting-service 传入 metadata 含 sourceAccountSequence 和中文 memo
- 历史记录(2026-01-04前):metadata 可能为空,显示为"-"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 20:22:40 -08:00
hailin 05aacc0d5b fix(admin-web): 系统账户流水备注列显示完整内容
移除 .memo 的 max-width/overflow/ellipsis/nowrap 截断样式,
改为 word-break: break-word 自动换行,确保备注全文可见。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 19:55:45 -08:00
hailin 62bbbca609 Revert "fix(ledger): REWARD_EXPIRED条目显示权益类型+已过期标签"
This reverts commit 4bd40970d0.
2026-02-28 12:04:07 -08:00
hailin 4bd40970d0 fix(ledger): REWARD_EXPIRED条目显示权益类型+已过期标签
- 奖励过期条目显示具体权益名(分享权益/省团队权益等)+ 红色"已过期"标签
- 图标改为灰色 timer_off,金额文字改为灰色,背景微灰
- 与正常权益收入条目有明显视觉区分

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 12:01:36 -08:00
hailin 724fb08be4 fix(contribution): 发布过期份额同步事件 + 管理后台/挖矿app状态显示
- contribution-service: swapContributionForMerge 作废旧份额记录后,
  立即发布 ContributionRecordSynced outbox 事件(isExpired=true),
  mining-admin-service 收到后 upsert syncedContributionRecord
- mining-admin-web: 算力记录状态列改为绿色"有效"/红色"无效"
- mining-app: 贡献值记录卡片始终显示有效/无效状态标签

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 11:35:55 -08:00
hailin a904c8bd42 feat(ledger): 预种份额在流水明细中显示合并合同下载按钮
- 移除硬编码"预种无合同"逻辑
- PPL 份额点击详情时,查找是否有对应的已签署合并记录
- 有签署合同则显示查看/下载按钮,调用预种合并合同 PDF 接口
- 同时新增 _viewMergeContractPdf / _downloadMergeContractPdf 方法

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 11:19:27 -08:00
hailin cd73b2dec4 fix(pre-planting): 签署合同前检查实名认证 + 修正合同金额
- getMergeContractPdf: KYC 为 null 时返回 400,不允许查看合同
- getMergeContractPdf: 从源订单汇总实际绿积分金额,CNY = 绿积分 × 1.1
- Flutter: KYC 错误时显示专用提示 + "去完成实名认证" 按钮

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 10:59:26 -08:00
hailin b1e5e6b29f feat(pre-planting): 合并合同走完整签署流程(PDF展示+手写签名)
- planting-service: 新增 GET /merges/:mergeNo/contract-pdf 接口,复用现有 PDF 模板
- planting-service: PrePlantingApplicationService 注入 PdfGeneratorService/IdentityServiceClient
- pre_planting_service.dart: 新增 downloadMergeContractPdf,signMergeContract 简化返回值
- 新建 PrePlantingMergeSigningPage:PDF展示→滚动到底→确认法律效力→手写签名→提交
- pending_contracts_page: 合并卡片点击跳签名页(prePlantingMergeSigning)
- pre_planting_merge_detail_page: 签署按钮跳签名页,移除直接调用逻辑
- 新增路由 /pre-planting/merge-signing/:mergeNo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 10:35:22 -08:00
hailin 2ad1936126 fix(pre-planting): 合并详情页 USDT 改为绿积分
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 10:17:12 -08:00
hailin 7bad0a8935 fix(pre-planting): 修复编译错误(getMerges→getMyMerges、RoutePaths 缺失导入、Future.wait 类型)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 10:00:17 -08:00
hailin b9b23c36d7 feat(pre-planting): 合并后走正常签合同流程,购买第5份直接跳合并详情页
- pre_planting_service: CreatePrePlantingOrderResponse 增加 merged/mergeNo 字段
- pre_planting_purchase_page: 购买成功若触发合并,直接跳转合并详情签合同
- contract_check_service: 注入 PrePlantingService,checkAll 增加预种待签合并检查
- pending_contracts_page: 同时展示普通合同和预种合并待签卡片,复用现有签合同弹窗流程
- injection_container: contractCheckServiceProvider 注入 prePlantingService

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 09:51:21 -08:00
hailin 26dcd1d2de fix(pre-planting): 修复购买省市名称存储及多项购买失败问题
== 问题修复 ==

1. 购买失败:NestJS 返回数组 message 导致 Flutter 类型转换错误
   - 症状:List<dynamic> is not a subtype of String
   - 原因:ValidationPipe 校验失败时 message 字段为 List<String>(每条字段错误一条),
     Flutter _handleDioError 直接用 data['message'] 作为 String 参数导致运行时崩溃
   - 修复:api_client.dart 中对 rawMsg 判断是否 List,若是则 join(', ')

2. 续购省市为空导致 400 校验失败
   - 症状:续购时后端返回 "provinceCode should not be empty"
   - 原因:购买页面续购分支未传入省市,导致 provinceCode/cityCode 为 null
   - 修复:pre_planting_purchase_page.dart 中续购时使用 _position?.provinceCode

3. 购买请求携带 provinceName/cityName 被后端 forbidNonWhitelisted 拒绝
   - 症状:400 "property provinceName should not exist"
   - 原因:前端发送名称字段,但 PurchasePrePlantingDto 未声明这些字段
   - 修复:在 DTO 中添加 @IsOptional() 的 provinceName / cityName 字段

== 功能新增 ==

4. 预种持仓表新增省市名称存储(参照正式认种的处理方式)
   - 迁移:20260228000000_add_province_city_name_to_position
   - Prisma schema:PrePlantingPosition 新增 provinceName / cityName 可空字段
   - 聚合根:addPortions() 接受可选 provinceName/cityName,首购时写入,续购忽略
   - Repository:save/toDomain 同步处理名称字段
   - Application Service:purchasePortion 透传名称,getPosition 返回名称
   - Controller:purchase 端点透传 dto.provinceName / dto.cityName

5. 预种合并时算力精确回滚(contribution-service)
   - 新增 9a-team 步骤:事务内查询即将作废的 TEAM_LEVEL/TEAM_BONUS 算力记录
   - 新增 9c-team 步骤:按账户聚合后精确 decrement 上游推荐人的各档位 pending 和 effective
   - 目的:确保旧份额算力精确回滚,避免新树算力 9d 叠加后造成双倍计入

== UI 优化 ==
   - 购买页面将 "USDT" 改为 "绿积分"(单价、总价、成功提示)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 08:02:14 -08:00
hailin eea38b2b86 fix(pre-planting): 购买页面和弹窗中 USDT 改为绿积分
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 07:20:51 -08:00
hailin 9b6effe63d debug(pre-planting): 添加购买流程详细日志以排查 List cast 错误
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 07:19:35 -08:00
hailin 606d3c0b22 fix(pre-planting): 修复购买失败时 List<dynamic> 类型转换错误和续购省市缺失
1. api_client.dart: NestJS validation error 返回 message 为数组时,
   用 join(', ') 转为字符串,避免直接传给 ApiException(String) 崩溃
2. pre_planting_purchase_page.dart: 续购时传 _position 中已保存的
   provinceCode/cityCode,满足后端 DTO 必填校验

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 06:56:49 -08:00
hailin 2d7b02aa96 fix(pre-planting): 修复预种页面 5 个 UI 问题(纯前端,零后端改动)
=== 问题 1:流水明细合同按钮 500 错误 ===
文件: ledger_detail_page.dart
原因: 预种订单(PPL 前缀)无合同,但流水详情弹窗显示「查看合同/下载合同」按钮
修复: _showTransactionDetail 检测 refOrderId.startsWith('PPL'),
      预种订单传 showContractButtons: false,弹窗不渲染合同按钮区

=== 问题 2:流水备注显示英文 ===
文件: ledger_detail_page.dart
原因: 备注字段存储的是 'Plant payment (from frozen)'(后端写入,不改后端)
修复: _TransactionDetailSheet 展示备注时,若订单号以 PPL 开头则显示「预种」

=== 问题 3:预种明细订单金额单位错误 ===
文件: pre_planting_position_page.dart
修复: '${order.totalAmount.toInt()} USDT' → '${order.totalAmount.toInt()} 绿积分'

=== 问题 4:省市显示数字代码(如 44 · 4401)===
文件: pre_planting_position_page.dart / pre_planting_purchase_page.dart
原因: provinceName/cityName 为 null 时回退显示 provinceCode/cityCode
修复:
  - position 页:条件改为 provinceName != null && cityName != null,无中文名则不显示省市行
  - purchase 页:加载时不再 fallback 到代码;锁定显示无名称时显示「已锁定」;
    购买确认弹窗省市行无名称时显示「-」

=== 问题 5:「合并进度」改为「合成进度」===
文件: pre_planting_position_page.dart / pre_planting_purchase_page.dart
修复: 两处 Text('合并进度') → Text('合成进度')

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 06:25:38 -08:00
hailin 97f8b7339f fix(auth): LOGIN 能力禁用后强制下线已登录用户
## 问题
管理员在后台禁用用户的 LOGIN 能力后,该用户仍然可以正常使用 mining-app。
原因是 LOGIN 检查只在登录/刷新 token 时执行,已持有有效 JWT(7天有效期)
的用户不会被影响,直到 token 过期才会被拦截。

## 修复

### 后端 - JwtAuthGuard (auth-service)
- 在 JWT 验证通过后,增加 LOGIN 能力实时检查
- 调用 CapabilityService.isCapabilityEnabled() 查询 Redis 缓存
- LOGIN 被禁用时返回 403 ForbiddenException("您的账户已被限制登录")
- 采用 fail-open 策略:Redis/DB 查询失败时放行,不影响正常用户
- 每次认证请求多一次 Redis GET(<1ms),对当前用户规模无性能影响

### 前端 - mining-app API Client
- 新增 onLoginDisabled 全局回调(类似现有的 onUnauthorized)
- Dio 拦截器检测 403 响应中包含"限制登录"关键词时触发回调
- 回调执行:清除用户状态 + 跳转登录页(与 401 处理一致)

## 影响范围
- 所有使用 @UseGuards(JwtAuthGuard) 的端点都会实时检查 LOGIN 能力
- 管理员禁用 LOGIN 后,用户下一次 API 请求即被拦截并强制下线
- 不影响公开端点(登录、注册等不经过 JwtAuthGuard 的接口)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 05:01:37 -08:00
hailin fe9a30df85 feat(mining-app): 接入 capabilitiesProvider 实现 UI 层能力适配
Phase 5 补充:在 mining-app Flutter 前端关键页面接入 capability 检查,
被禁用的功能按钮置灰并显示 SnackBar 提示,后端 403 拦截作为兜底。

修改的页面及对应能力:
- send_shares_page: P2P_SEND → "确认发送"按钮
- receive_shares_page: P2P_RECEIVE → 顶部限制提示横幅
- c2c_market_page: C2C → "发布"按钮 + "接单"弹窗入口
- c2c_publish_page: C2C → "发布买入/卖出"按钮
- trading_page: TRADING → "确认交易"按钮
- edit_profile_page: PROFILE_EDIT → "保存"操作
- team_page: VIEW_TEAM → 页面数据加载前检查

设计原则:
- 不阻断页面浏览,只阻断操作
- fail-open: 能力获取失败时默认全部开启
- 禁用时点击按钮弹出 SnackBar 提示"您的XX功能已被限制"
- 沿用现有 disabled 按钮样式(.withOpacity(0.4))

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 01:36:24 -08:00