临时备份一次

This commit is contained in:
2026-07-13 12:01:18 +09:00
parent 5cdcdad534
commit 2e130b98f5
17 changed files with 3938 additions and 0 deletions

View File

@@ -0,0 +1,264 @@
# 需求17信用额度
> DDD 结构:`internal/domain/wallet/`
---
## 业务规则
### 两类主体
| 主体 | 信用额度来源 | 修改条件 |
|------|------------|---------|
| 代理(店铺) | 创建店铺时设置 `credit_limit` | 修改前必须先清零欠款(`balance >= 0`|
| 平台员工 | 基于**角色**,不是账号 | 角色权限表里配置 |
### 可用余额公式
```
可用额度 = balance + credit_limit - frozen_balance
```
- `balance` 可以为负(表示欠款)
- `balance < 0` 时表示已在使用信用额度
- `balance < -credit_limit` 表示超额使用(理论上不可达,系统保护)
### BPO-009 "是否可授权额度"开关
- 代理创建时,新增 `enable_credit` 开关
- 开关关闭 = `credit_limit` 强制为 0不允许使用信用额度
- 平台员工账号通过角色自动拥有额度,无需开关
### BPO-012 负余额显示
- 代理前台AgentWallet`balance` 正常展示(可为负)
- 前端不做"不能为负"的前端校验,由后端控制
---
## 数据库变更
### 1. Shop 表加信用额度字段
```sql
ALTER TABLE tb_shop
ADD COLUMN enable_credit BOOLEAN NOT NULL DEFAULT FALSE
COMMENT '是否可使用信用额度',
ADD COLUMN credit_limit BIGINT NOT NULL DEFAULT 0
COMMENT '授信额度上限(分)';
```
### 2. AgentWallet 表加信用额度字段
```sql
ALTER TABLE tb_agent_wallet
ADD COLUMN credit_limit BIGINT NOT NULL DEFAULT 0
COMMENT '信用额度(分),与 shop.credit_limit 同步,余额可到 -credit_limit';
```
> 钱包上冗余一份,是为了避免每次扣款都要 join shop 表。通过 Shop 修改额度时同步更新钱包。
### 3. 角色信用额度配置(平台员工)
```sql
ALTER TABLE tb_role
ADD COLUMN credit_limit BIGINT NOT NULL DEFAULT 0
COMMENT '该角色的信用额度(分),仅对平台员工角色有效';
```
---
## Model 变更
```go
// internal/model/shop.go
type Shop struct {
// ...原有字段...
EnableCredit bool `gorm:"column:enable_credit;not null;default:false;comment:是否可使用信用额度" json:"enable_credit"`
CreditLimit int64 `gorm:"column:credit_limit;type:bigint;not null;default:0;comment:授信额度上限(分)" json:"credit_limit"`
}
// internal/model/agent_wallet.go
type AgentWallet struct {
// ...原有字段Balance, FrozenBalance 已存在)...
CreditLimit int64 `gorm:"column:credit_limit;type:bigint;not null;default:0;comment:信用额度(分)" json:"credit_limit"`
}
// GetAvailableBalance 更新:可用余额 = 余额 - 冻结 + 信用额度
func (w *AgentWallet) GetAvailableBalance() int64 {
return w.Balance - w.FrozenBalance + w.CreditLimit
}
```
---
## 领域层DDD
```go
// internal/domain/wallet/wallet.go
// AgentWallet 钱包聚合根(扩展现有模型)
type AgentWalletAggregate struct {
model.AgentWallet
domainEvents []DomainEvent `gorm:"-"`
}
// Debit 扣款(含信用额度)
func (w *AgentWalletAggregate) Debit(amountCents int64, refType string, refID uint) error {
available := w.Balance - w.FrozenBalance + w.CreditLimit
if available < amountCents {
return ErrInsufficientBalance
}
before := w.Balance
w.Balance -= amountCents
w.recordEvent(WalletDebitedEvent{
WalletID: w.ID,
ShopID: w.ShopID,
Amount: amountCents,
BalanceBefore: before,
BalanceAfter: w.Balance,
RefType: refType,
RefID: refID,
})
return nil
}
```
---
## 应用层
```go
// internal/application/wallet/grant_credit.go
// GrantCreditCommand 授信命令
type GrantCreditCommand struct {
ShopID uint
CreditLimit int64 // 分
OperatorID uint
}
// GrantCreditUseCase 设置代理信用额度
type GrantCreditUseCase struct {
shopStore ShopStore
walletStore WalletStore
txManager TxManager
}
func (uc *GrantCreditUseCase) Execute(ctx context.Context, cmd GrantCreditCommand) error {
return uc.txManager.RunInTx(ctx, func(tx *gorm.DB) error {
shop, err := uc.shopStore.GetByID(ctx, cmd.ShopID)
if err != nil { return err }
// 修改信用额度前,必须当前余额 >= 0无欠款
wallet, err := uc.walletStore.GetMainWallet(ctx, cmd.ShopID)
if err != nil { return err }
if wallet.Balance < 0 {
return errors.New(errors.CodeForbidden, "存在欠款,请先清零后再修改信用额度")
}
// 同步更新 shop 和 wallet
if err := uc.shopStore.UpdateCreditLimit(ctx, tx, cmd.ShopID, cmd.CreditLimit); err != nil {
return err
}
return uc.walletStore.UpdateCreditLimit(ctx, tx, wallet.ID, cmd.CreditLimit)
})
}
```
---
## API 设计
### 1. 创建店铺(新增字段)
```
POST /admin/shops
```
新增参数:
```go
EnableCredit bool `json:"enable_credit" description:"是否开启信用额度"`
CreditLimit int64 `json:"credit_limit" description:"授信额度enable_credit=true 时有效"`
```
### 2. 修改信用额度
```
PUT /admin/shops/{id}/credit-limit
```
```go
type UpdateCreditLimitRequest struct {
CreditLimit int64 `json:"credit_limit" validate:"min=0" description:"新授信额度(分),修改前需余额>=0"`
}
```
错误场景:余额为负时,返回 `CodeForbidden`msg="存在欠款(-XX元请先清零后再修改信用额度"
### 3. 角色信用额度配置(平台员工)
```
PUT /admin/roles/{id}/credit-limit
```
```go
type UpdateRoleCreditLimitRequest struct {
CreditLimit int64 `json:"credit_limit" validate:"min=0" description:"该角色的信用额度(分)"`
}
```
### 4. 查询钱包(余额展示更新)
现有 `GET /admin/agent-wallets/{shopID}` 响应新增字段:
```go
type AgentWalletResponse struct {
// ...原有字段...
CreditLimit int64 `json:"credit_limit" description:"信用额度(分)"`
AvailableBalance int64 `json:"available_balance" description:"可用余额 = balance - frozen + credit_limit可为负"`
IsInDebt bool `json:"is_in_debt" description:"是否有欠款balance < 0"`
DebtAmount int64 `json:"debt_amount" description:"欠款金额balance<0时有值"`
}
```
---
## 前端对接
### 页面:创建/编辑代理
新增"信用额度"区块:
```
信用额度:
[☑ 开启信用额度]
授信上限:[____] 元 enable_credit=true 时展示)
```
提交时:`enable_credit: true, credit_limit: 100000`(单位:分,前端转换)
### 页面:代理详情 > 钱包信息
```
钱包余额:-¥200.00 (红色显示)
信用额度¥1,000.00
可用余额¥800.00
```
`is_in_debt=true` 时,余额展示红色,并加"欠款"标签。
### 页面:修改信用额度
入口:代理详情 > 钱包 > "修改信用额度"按钮
弹框:输入新额度 → `PUT /admin/shops/{id}/credit-limit`
如果当前有欠款(`is_in_debt=true`),禁用该按钮,展示"存在欠款,无法修改信用额度"。
### 页面:余额显示(全局)
代理管理列表的余额列:支持显示负数,负数红色标注。
### 平台员工余额(角色信用)
平台员工操作(如批量订购代理余额扣款)时,可用余额 = 当前余额 + 角色信用额度。
在批量订购确认页展示可用额度。