# Proposal: refactor-data-permission-filter

## Why

当前 GORM Callback 自动数据权限过滤机制存在以下问题：

1. **跳过率高** - 20+ 处使用 `SkipDataPermission`（异步任务、登录、复杂查询等场景都需要跳过）
2. **特殊处理多** - `tb_shop`、`tb_tag`、`tb_enterprise_card_authorization` 等表需要特殊逻辑
3. **重复查询** - 同一请求中 Service 层的 `CanManageShop` 和 Callback 都调用 `GetSubordinateShopIDs`
4. **隐式行为** - 开发者不知道 GORM 查询被加了什么条件，调试困难
5. **原生 SQL 失效** - 原生 SQL 无法自动应用，需要手动在 Store 里重复写权限过滤逻辑

需要重构为显式调用模式，让数据权限过滤行为可预测、可控。

## What Changes

### 新增

- **中间件预加载**：扩展 Auth 中间件，对代理用户预计算 `SubordinateShopIDs` 并放入 Context
- **UserContextInfo 扩展**：新增 `SubordinateShopIDs []uint` 字段
- **Helper 函数**：
  - `GetSubordinateShopIDs(ctx) []uint` - 获取下级店铺 ID 列表
  - `ApplyShopFilter(ctx, query) *gorm.DB` - 应用 `WHERE shop_id IN ?` 过滤
  - `ApplyEnterpriseFilter(ctx, query) *gorm.DB` - 应用 `WHERE enterprise_id = ?` 过滤
  - `ApplyOwnerShopFilter(ctx, query) *gorm.DB` - 应用 `WHERE owner_shop_id IN ?` 过滤

### 移除

- **BREAKING**：移除 `RegisterDataPermissionCallback` 函数
- **BREAKING**：移除 `SkipDataPermission` 函数及所有调用点（20+ 处）
- **BREAKING**：`CanManageShop` / `CanManageEnterprise` 函数签名变更，不再需要 Store 参数

### 修改

- **Store 层显式过滤**：16 个 Store、85+ 个查询方法需要显式调用 Helper 函数添加权限过滤

## Capabilities

### New Capabilities

- `data-scope-middleware`: 数据权限范围中间件，负责预计算用户的数据访问范围并注入 Context

### Modified Capabilities

- `data-permission`: 数据权限过滤机制从 GORM Callback 自动过滤改为业务层显式调用

## Impact

### 代码影响

| 类型 | 文件/模块 | 改动说明 |
|------|-----------|----------|
| 新增 | `pkg/middleware/data_scope.go` | Helper 函数和 Context 操作 |
| 修改 | `pkg/middleware/auth.go` | 扩展 `UserContextInfo`，Auth 中间件预计算 |
| 修改 | `pkg/middleware/permission_helper.go` | `CanManageShop` 等函数改为从 Context 取数据 |
| 移除 | `pkg/gorm/callback.go` | 移除 `RegisterDataPermissionCallback` |
| 修改 | 16 个 Store 文件 | 显式调用过滤 Helper 函数 |
| 移除 | 20+ 处 `SkipDataPermission` 调用 | 不再需要跳过机制 |

### 需要改动的 Store（按复杂度分级）

**高复杂度（3 个）**：
- `iot_card_store.go` - 9+ 方法，已有部分手动实现
- `account_store.go` - 7+ 方法，双字段过滤（shop_id / enterprise_id）
- `enterprise_card_authorization_store.go` - 8+ 方法，已有参考实现

**中复杂度（4 个）**：
- `device_store.go` - 4+ 方法，NULL shop_id 表示平台库存
- `order_store.go` - 3 方法，使用 seller_shop_id
- `shop_package_allocation_store.go` - 6 方法
- `shop_series_allocation_store.go` - 6 方法

**低复杂度（9 个）**：
- `agent_wallet_store.go` - 5 方法
- `agent_wallet_transaction_store.go` - 4 方法
- `commission_record_store.go` - 4 方法
- `enterprise_device_authorization_store.go` - 5 方法
- `enterprise_store.go` - 3 方法
- `shop_role_store.go` - 2 方法
- `iot_card_import_task_store.go` - 2 方法
- `commission_withdrawal_request_store.go` - 3 方法
- `card_wallet_store.go` - 5+ 方法

### API 影响

- 无外部 API 变更
- 内部函数签名变更：`CanManageShop(ctx, targetShopID, shopStore)` → `CanManageShop(ctx, targetShopID)`

### 行为变更

- NULL `shop_id` 的记录对代理用户不可见（保持现有行为）
- 平台用户/超管不受数据权限限制（保持现有行为）
- 数据权限过滤从隐式变为显式，需要业务层主动调用
