feat(distribution): 默认结算冷却天数改为系统配置驱动
新建站点的默认冷却期原先硬编码 7 天(OnboardSite 字面量、DB 列默认、两套前端表单), 改一处要同时动三处。现统一到系统配置键 distribution.default_settlement_hold_days: - systemconfig 注册该键(默认 7,0=立即释放,0-365 校验),纳入 admin 可见配置 - distribution 建站两条路径(CreateSite 未传值、OnboardSite 缺省)读取该配置,缺失或非法回落 7 - 新增 GET /admin/distribution/site-defaults 暴露当前有效默认值 - Vue / React 站点表单改为运行时拉取,拉取失败保持 7 - DB 列默认值保留作为兜底,站点级 settlement_hold_days 仍可单独覆盖
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
package systemconfig
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DistributionSettlementHoldDaysKey 是「新建分销站点默认结算冷却天数」的系统配置键。
|
||||
const DistributionSettlementHoldDaysKey = "distribution.default_settlement_hold_days"
|
||||
|
||||
// DefaultDistributionSettlementHoldDays 是新建分销站点默认结算冷却天数的兜底值。
|
||||
const DefaultDistributionSettlementHoldDays = 7
|
||||
|
||||
func defaultDistributionSettlementHoldDaysValue() string {
|
||||
return strconv.Itoa(DefaultDistributionSettlementHoldDays)
|
||||
}
|
||||
|
||||
// ParseDistributionSettlementHoldDays 解析结算冷却天数配置,为空、非法或越界时回落到默认 7 天。
|
||||
func ParseDistributionSettlementHoldDays(raw string) int {
|
||||
days, err := strconv.Atoi(strings.TrimSpace(raw))
|
||||
if err != nil || days < 0 || days > 365 {
|
||||
return DefaultDistributionSettlementHoldDays
|
||||
}
|
||||
return days
|
||||
}
|
||||
|
||||
// validateConfigValue 按配置键分发校验逻辑,未注册校验的键直接放行。
|
||||
func validateConfigValue(key, value string) error {
|
||||
switch key {
|
||||
case DistributionSettlementHoldDaysKey:
|
||||
return validateDistributionSettlementHoldDays(value)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func validateDistributionSettlementHoldDays(value string) error {
|
||||
days, err := strconv.Atoi(strings.TrimSpace(value))
|
||||
if err != nil || days < 0 || days > 365 {
|
||||
return ErrInvalidConfig
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package systemconfig
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseDistributionSettlementHoldDays(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
raw string
|
||||
want int
|
||||
}{
|
||||
{name: "零天立即释放", raw: "0", want: 0},
|
||||
{name: "默认七天", raw: "7", want: 7},
|
||||
{name: "上限三百六十五天", raw: "365", want: 365},
|
||||
{name: "负数回落", raw: "-1", want: DefaultDistributionSettlementHoldDays},
|
||||
{name: "超过上限回落", raw: "366", want: DefaultDistributionSettlementHoldDays},
|
||||
{name: "空值回落", raw: " ", want: DefaultDistributionSettlementHoldDays},
|
||||
{name: "非数字回落", raw: "abc", want: DefaultDistributionSettlementHoldDays},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := ParseDistributionSettlementHoldDays(tc.raw); got != tc.want {
|
||||
t.Fatalf("ParseDistributionSettlementHoldDays(%q) = %d, want %d", tc.raw, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDistributionSettlementHoldDaysConfigRegistered(t *testing.T) {
|
||||
value := ""
|
||||
for _, item := range defaultConfigs {
|
||||
if item.Key == DistributionSettlementHoldDaysKey {
|
||||
value = item.Value
|
||||
break
|
||||
}
|
||||
}
|
||||
if value != defaultDistributionSettlementHoldDaysValue() {
|
||||
t.Fatalf("defaultConfigs 中 %s 的默认值 = %q, want %q", DistributionSettlementHoldDaysKey, value, defaultDistributionSettlementHoldDaysValue())
|
||||
}
|
||||
visible := false
|
||||
for _, key := range adminVisibleConfigKeys {
|
||||
if key == DistributionSettlementHoldDaysKey {
|
||||
visible = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !visible {
|
||||
t.Fatalf("adminVisibleConfigKeys 缺少 %s", DistributionSettlementHoldDaysKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateConfigValueDistributionSettlementHoldDays(t *testing.T) {
|
||||
for _, value := range []string{"0", "7", "365"} {
|
||||
if err := validateConfigValue(DistributionSettlementHoldDaysKey, value); err != nil {
|
||||
t.Fatalf("validateConfigValue(%q) = %v, want nil", value, err)
|
||||
}
|
||||
}
|
||||
for _, value := range []string{"-1", "366", "", "abc"} {
|
||||
if err := validateConfigValue(DistributionSettlementHoldDaysKey, value); err != ErrInvalidConfig {
|
||||
t.Fatalf("validateConfigValue(%q) = %v, want ErrInvalidConfig", value, err)
|
||||
}
|
||||
}
|
||||
// 未注册校验的键直接放行。
|
||||
if err := validateConfigValue("handoff.owner_submit_timeout_minutes", "abc"); err != nil {
|
||||
t.Fatalf("无关配置键应放行, got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ var defaultConfigs = []defaultConfig{
|
||||
{Key: cooperationFeedbackConfigKey, Value: defaultCooperationFeedbackConfigValue(), Description: "首页 合作与反馈入口配置 JSON(标题/副标题/展示图片)"},
|
||||
{Key: publishOptionsConfigKey, Value: defaultPublishOptionsConfigValue(), Description: "发布页选项配置 JSON"},
|
||||
{Key: salePriceConfigKey, Value: defaultSalePriceConfigValue(), Description: "内部出售定价规则 JSON"},
|
||||
{Key: DistributionSettlementHoldDaysKey, Value: defaultDistributionSettlementHoldDaysValue(), Description: "新建分销站点默认结算冷却天数(0=立即释放,最大 365)"},
|
||||
{Key: "backup.schedule_enabled", Value: "true", Description: "在线备份定时任务是否启用"},
|
||||
{Key: "backup.full_hour", Value: "4", Description: "每日全量备份小时(Asia/Shanghai)"},
|
||||
{Key: "backup.full_minute", Value: "30", Description: "每日全量备份分钟(Asia/Shanghai)"},
|
||||
@@ -65,6 +66,7 @@ var adminVisibleConfigKeys = []string{
|
||||
"chat.default_support_admin_id",
|
||||
"chat.listing_group_welcome",
|
||||
"chat.renter_retention_days_after_order_end",
|
||||
"distribution.default_settlement_hold_days",
|
||||
"handoff.owner_return_confirm_timeout_minutes",
|
||||
"handoff.owner_submit_timeout_minutes",
|
||||
"handoff.renter_confirm_timeout_minutes",
|
||||
@@ -140,6 +142,9 @@ func (r *Repository) FindValue(ctx context.Context, key string) (string, error)
|
||||
}
|
||||
|
||||
func (r *Repository) Update(ctx context.Context, actorID uint64, key string, req UpdateRequest, meta AuditMeta) (*ConfigDTO, error) {
|
||||
if err := validateConfigValue(key, req.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var row model.SystemConfig
|
||||
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("`key` = ?", key).First(&row).Error; err != nil {
|
||||
|
||||
Reference in New Issue
Block a user