From dbe9aaabfe5b7697651e7f3155bfc785197ebfb6 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Thu, 10 Sep 2026 18:44:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(distribution):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E7=94=9F=E4=BA=A7=E7=8E=AF=E5=A2=83=E8=B5=84=E9=87=91=E5=86=BB?= =?UTF-8?q?=E7=BB=93=EF=BC=8C=E6=B5=8B=E8=AF=95=E4=B8=8E=E7=94=9F=E4=BA=A7?= =?UTF-8?q?=E5=8F=A3=E5=BE=84=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit liveMoneyMovementEnabled 按 APP_ENV 在生产冻结结算入账、站点提现与自动释放, 与测试环境行为不一致,且该开关在 APP_ENV 缺失时为 fail-open。本次整体移除: - 删除 liveMoneyMovementEnabled 与 ErrLiveMoneyMovementFrozen - MarkSettlementBatch / ApplySiteWithdrawal / AutoReleaseDueSettlements 去掉守卫 - handler 去掉 403 settlement_frozen 映射 - 测试改为断言生产环境结算入账与提现正常执行 --- .../internal/modules/distribution/handler.go | 2 -- .../internal/modules/distribution/release.go | 3 --- .../modules/distribution/repository.go | 13 --------- .../modules/distribution/repository_test.go | 27 ++++++++++++------- docs/分销网管理后台设计方案.md | 2 +- 5 files changed, 19 insertions(+), 28 deletions(-) diff --git a/backend/internal/modules/distribution/handler.go b/backend/internal/modules/distribution/handler.go index 1dab02a..548ef69 100644 --- a/backend/internal/modules/distribution/handler.go +++ b/backend/internal/modules/distribution/handler.go @@ -787,8 +787,6 @@ func writeError(c *gin.Context, err error) { response.BadRequest(c, "请选择要加价的商品") case errors.Is(err, ErrInvalidListingStatus): response.BadRequest(c, "商品展示状态不正确") - case errors.Is(err, ErrLiveMoneyMovementFrozen): - response.Error(c, http.StatusForbidden, "settlement_frozen", "分销结算与提现已冻结,待分成口径确认后再开放") case errors.Is(err, ErrSiteAccessDenied): response.Error(c, http.StatusForbidden, "forbidden", "无权访问该分销站点") case errors.Is(err, ErrDependencyUnavailable): diff --git a/backend/internal/modules/distribution/release.go b/backend/internal/modules/distribution/release.go index 655df1a..82cbd97 100644 --- a/backend/internal/modules/distribution/release.go +++ b/backend/internal/modules/distribution/release.go @@ -29,9 +29,6 @@ func (r *Repository) AutoReleaseDueSettlements(ctx context.Context) (int, error) if r == nil || r.db == nil { return 0, ErrDependencyUnavailable } - if !liveMoneyMovementEnabled() { - return 0, ErrLiveMoneyMovementFrozen - } now := time.Now() var orderIDs []uint64 if err := r.db.WithContext(ctx). diff --git a/backend/internal/modules/distribution/repository.go b/backend/internal/modules/distribution/repository.go index f318b77..5288bea 100644 --- a/backend/internal/modules/distribution/repository.go +++ b/backend/internal/modules/distribution/repository.go @@ -7,7 +7,6 @@ import ( "encoding/json" "errors" "fmt" - "os" "strings" "time" @@ -41,7 +40,6 @@ var ( ErrAdminDisabled = errors.New("admin disabled") ErrDuplicateSiteCodeOrDomain = errors.New("distribution site code or domain already exists") ErrDuplicateUsername = errors.New("username already exists") - ErrLiveMoneyMovementFrozen = errors.New("distribution settlement and withdrawal frozen until split rules are verified") ErrTooManySyncListings = errors.New("too many listings to sync") ErrSyncListingNotFound = errors.New("sync listing not found") ErrEmptyListingIDs = errors.New("listing ids required") @@ -54,11 +52,6 @@ var settlementReadyOrderStatuses = []string{"completed", "closed"} const maxSyncListingIDs = 200 -func liveMoneyMovementEnabled() bool { - env := strings.ToLower(strings.TrimSpace(os.Getenv("APP_ENV"))) - return env != "production" && env != "prod" -} - // normalizeSettlementHoldDays 校验站点结算冷却天数,允许 0(订单完成即释放)到 365 天。 func normalizeSettlementHoldDays(days int) (int, error) { if days < 0 || days > 365 { @@ -1021,9 +1014,6 @@ func (r *Repository) CreateSettlementBatch(ctx context.Context, adminID uint64, } func (r *Repository) MarkSettlementBatch(ctx context.Context, adminID, id uint64) (*SettlementBatchDTO, error) { - if !liveMoneyMovementEnabled() { - return nil, ErrLiveMoneyMovementFrozen - } var row model.DistributionSettlementBatch err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&row, id).Error; err != nil { @@ -2056,9 +2046,6 @@ func (r *Repository) GetSiteWallet(ctx context.Context, adminID, siteID uint64) } func (r *Repository) ApplySiteWithdrawal(ctx context.Context, adminID, siteID uint64, req ApplySiteWithdrawalRequest) (*SiteWithdrawalDTO, error) { - if !liveMoneyMovementEnabled() { - return nil, ErrLiveMoneyMovementFrozen - } if siteID == 0 { return nil, ErrSiteNotFound } diff --git a/backend/internal/modules/distribution/repository_test.go b/backend/internal/modules/distribution/repository_test.go index c25febb..80fc992 100644 --- a/backend/internal/modules/distribution/repository_test.go +++ b/backend/internal/modules/distribution/repository_test.go @@ -1038,16 +1038,16 @@ func TestSettlementBatchSplitsThreePartyBySite(t *testing.T) { } } -func TestLiveMoneyMovementFrozenInProduction(t *testing.T) { +func TestMoneyMovementNotFrozenInProduction(t *testing.T) { t.Setenv("APP_ENV", "production") db := distributionTestDB(t) repo := NewRepository(db) - site := model.DistributionSite{SiteCode: "frozen", SiteName: "冻结站", Domain: "frozen.example.com", Status: "active"} + site := model.DistributionSite{SiteCode: "prod", SiteName: "生产口径站", Domain: "prod.example.com", Status: "active"} if err := db.Create(&site).Error; err != nil { t.Fatal(err) } batch := model.DistributionSettlementBatch{ - SiteID: site.ID, BatchNo: "B-FROZEN", PeriodStart: time.Now().Add(-time.Hour), PeriodEnd: time.Now(), + SiteID: site.ID, BatchNo: "B-PROD", PeriodStart: time.Now().Add(-time.Hour), PeriodEnd: time.Now(), TotalAmountCent: 100, Status: "draft", } if err := db.Create(&batch).Error; err != nil { @@ -1055,7 +1055,7 @@ func TestLiveMoneyMovementFrozenInProduction(t *testing.T) { } // 结算明细的 order_id / party_id 都带外键,必须先落库对应的订单与参与方。 order := model.RentalOrder{ - OrderNo: "ORD-FROZEN", ListingID: 1, AccountID: 1, OwnerID: 1, RenterID: 1, + OrderNo: "ORD-PROD", ListingID: 1, AccountID: 1, OwnerID: 1, RenterID: 1, Status: "completed", SettlementMode: "platform_managed", } if err := db.Create(&order).Error; err != nil { @@ -1073,13 +1073,22 @@ func TestLiveMoneyMovementFrozenInProduction(t *testing.T) { }).Error; err != nil { t.Fatal(err) } - if _, err := repo.MarkSettlementBatch(t.Context(), 0, batch.ID); err != ErrLiveMoneyMovementFrozen { - t.Fatalf("settle err = %v", err) + // 生产与测试口径一致:结算入账与站点提现不再按 APP_ENV 冻结。 + settled, err := repo.MarkSettlementBatch(t.Context(), 0, batch.ID) + if err != nil { + t.Fatalf("生产环境结算入账不应被冻结: %v", err) } - if _, err := repo.ApplySiteWithdrawal(t.Context(), 0, site.ID, ApplySiteWithdrawalRequest{ + if settled.Status != "settled" { + t.Fatalf("batch status = %s, want settled", settled.Status) + } + wd, err := repo.ApplySiteWithdrawal(t.Context(), 0, site.ID, ApplySiteWithdrawalRequest{ SiteID: site.ID, AmountCent: 1, AccountType: "alipay", AccountName: "x", AccountNo: "1", - }); err != ErrLiveMoneyMovementFrozen { - t.Fatalf("withdraw err = %v", err) + }) + if err != nil { + t.Fatalf("生产环境站点提现不应被冻结: %v", err) + } + if wd.AmountCent != 1 || wd.Status != "pending" { + t.Fatalf("unexpected withdrawal: %+v", wd) } } diff --git a/docs/分销网管理后台设计方案.md b/docs/分销网管理后台设计方案.md index 77d6263..494bbd8 100644 --- a/docs/分销网管理后台设计方案.md +++ b/docs/分销网管理后台设计方案.md @@ -827,5 +827,5 @@ distribution.hao858.com { 4. 分站货架自主提价溢价(`shelf_markup_cent`)100% 独享归卖方分站,不参与三方切分; 5. 兑换比例提价语义明确(比例越小售价越高,提价使分站售价增加)。 - 管理 API 覆盖站点、规则、售出/收号订单查询、财务快照、账号展示开关、支付通道绑定和结算批次。 -- 生产环境默认保持结算入账与站点提现冻结(`liveMoneyMovementEnabled`),待全链路财务验收后开放。 +- 生产与测试口径一致:结算批次入账、站点提现与冷却期自动释放不再按 `APP_ENV` 冻结(原 `liveMoneyMovementEnabled` 生产保护已移除)。 - `distribution-admin` 基于 React + TypeScript + Vite + Ant Design,覆盖经营大盘、双视图资产管理、选品货架、订单中心、品牌装修与钱包明细。