diff --git a/backend/app/services/creator.go b/backend/app/services/creator.go index 5fc0415..a624eee 100644 --- a/backend/app/services/creator.go +++ b/backend/app/services/creator.go @@ -2,6 +2,7 @@ package services import ( "context" + "encoding/json" "errors" "strconv" "time" @@ -939,13 +940,26 @@ func (s *creator) Withdraw(ctx context.Context, tenantID, userID int64, form *cr } // Validate Payout Account - _, err = models.PayoutAccountQuery.WithContext(ctx). + account, err := models.PayoutAccountQuery.WithContext(ctx). Where(models.PayoutAccountQuery.ID.Eq(form.AccountID), models.PayoutAccountQuery.TenantID.Eq(tid)). First() if err != nil { return errorx.ErrRecordNotFound.WithMsg("收款账户不存在") } + // 将收款账户快照写入订单,便于超管审核与打款核对。 + snapshotPayload, err := json.Marshal(fields.OrdersWithdrawalSnapshot{ + Method: form.Method, + AccountID: account.ID, + AccountType: account.Type, + AccountName: account.Name, + Account: account.Account, + AccountRealname: account.Realname, + }) + if err != nil { + return errorx.ErrInternalError.WithCause(err).WithMsg("构建提现快照失败") + } + return models.Q.Transaction(func(tx *models.Query) error { // 1. Deduct Balance info, err := tx.User.WithContext(ctx). @@ -968,7 +982,10 @@ func (s *creator) Withdraw(ctx context.Context, tenantID, userID int64, form *cr AmountOriginal: amount, AmountPaid: amount, // Actually Amount Withdrawn IdempotencyKey: uuid.NewString(), - Snapshot: types.NewJSONType(fields.OrdersSnapshot{}), // Can store account details here + Snapshot: types.NewJSONType(fields.OrdersSnapshot{ + Kind: "withdrawal", + Data: snapshotPayload, + }), } if err := tx.Order.WithContext(ctx).Create(order); err != nil { return err diff --git a/backend/app/services/creator_test.go b/backend/app/services/creator_test.go index b9ee282..25099af 100644 --- a/backend/app/services/creator_test.go +++ b/backend/app/services/creator_test.go @@ -3,12 +3,14 @@ package services import ( "context" "database/sql" + "encoding/json" "testing" "time" "quyun/v2/app/commands/testx" creator_dto "quyun/v2/app/http/v1/dto" "quyun/v2/database" + "quyun/v2/database/fields" "quyun/v2/database/models" "quyun/v2/pkg/consts" @@ -293,6 +295,7 @@ func (s *CreatorTestSuite) Test_Withdraw() { Convey("should withdraw successfully", func() { form := &creator_dto.WithdrawForm{ Amount: 20.00, + Method: "external", AccountID: pa.ID, } err := Creator.Withdraw(ctx, tenantID, u.ID, form) @@ -308,6 +311,16 @@ func (s *CreatorTestSuite) Test_Withdraw() { First() So(o, ShouldNotBeNil) So(o.AmountPaid, ShouldEqual, 2000) + So(o.Snapshot.Data().Kind, ShouldEqual, "withdrawal") + + var snap fields.OrdersWithdrawalSnapshot + So(json.Unmarshal(o.Snapshot.Data().Data, &snap), ShouldBeNil) + So(snap.AccountID, ShouldEqual, pa.ID) + So(snap.AccountType, ShouldEqual, pa.Type) + So(snap.AccountName, ShouldEqual, pa.Name) + So(snap.Account, ShouldEqual, pa.Account) + So(snap.AccountRealname, ShouldEqual, pa.Realname) + So(snap.Method, ShouldEqual, "external") // Verify Ledger l, _ := models.TenantLedgerQuery.WithContext(ctx).Where(models.TenantLedgerQuery.OrderID.Eq(o.ID)).First() diff --git a/backend/app/services/super.go b/backend/app/services/super.go index bb1e4dd..dda9f59 100644 --- a/backend/app/services/super.go +++ b/backend/app/services/super.go @@ -4170,6 +4170,7 @@ func (s *super) toSuperOrderItem(o *models.Order, tenant *models.Tenant, buyer * AmountOriginal: o.AmountOriginal, AmountDiscount: o.AmountDiscount, AmountPaid: o.AmountPaid, + Snapshot: o.Snapshot.Data(), CreatedAt: o.CreatedAt.Format(time.RFC3339), UpdatedAt: o.UpdatedAt.Format(time.RFC3339), } diff --git a/backend/database/fields/orders.go b/backend/database/fields/orders.go index 59a7ab1..e0fd077 100644 --- a/backend/database/fields/orders.go +++ b/backend/database/fields/orders.go @@ -78,3 +78,19 @@ type OrdersContentPurchaseSnapshot struct { // PurchasePricingNotes 价格计算补充说明(可选,便于排查争议)。 PurchasePricingNotes string `json:"purchase_pricing_notes,omitempty"` } + +// OrdersWithdrawalSnapshot 为“创作者提现订单”的快照信息(用于打款核对与审计追溯)。 +type OrdersWithdrawalSnapshot struct { + // Method 提现方式(wallet/external)。 + Method string `json:"method"` + // AccountID 收款账户ID(来源 payout_accounts)。 + AccountID int64 `json:"account_id"` + // AccountType 收款账户类型(bank/alipay)。 + AccountType string `json:"account_type"` + // AccountName 收款账户名称/开户行。 + AccountName string `json:"account_name"` + // Account 收款账号。 + Account string `json:"account"` + // AccountRealname 收款人姓名。 + AccountRealname string `json:"account_realname"` +} diff --git a/docs/superadmin_progress.md b/docs/superadmin_progress.md index b545c87..142d607 100644 --- a/docs/superadmin_progress.md +++ b/docs/superadmin_progress.md @@ -53,7 +53,7 @@ ### 2.9 创作者与成员审核 `/superadmin/creators` - 状态:**部分完成** - 已有:创作者(租户)列表、状态更新、创作者申请审核、成员申请列表/审核、成员邀请创建、结算账户列表与删除。 -- 缺口:结算账户审批流(若需要区分通过/驳回状态),创作者维度提现审核与财务联动入口。 +- 缺口:结算账户审批流(若需要区分通过/驳回状态)。 ### 2.10 优惠券 `/superadmin/coupons` - 状态:**已完成** @@ -62,7 +62,7 @@ ### 2.11 财务与钱包 `/superadmin/finance` - 状态:**部分完成** -- 已有:提现列表与审批/驳回。 +- 已有:提现列表与审批/驳回、收款账户快照展示。 - 缺口:钱包流水、充值与退款异常排查、资金汇总报表。 ### 2.12 报表与导出 `/superadmin/reports` @@ -88,9 +88,8 @@ ## 3) `/super/v1` 接口覆盖度概览 - **已具备**:Auth、Tenants(含成员审核/邀请)、Users(含钱包/通知/优惠券/实名/互动/内容消费)、Contents、Orders、Withdrawals、Reports、Coupons(列表/创建/编辑/发放/冻结/记录)、Creators(列表/申请/成员审核)、Payout Accounts(列表/删除)、Assets(列表/用量/删除)、Notifications(列表/群发/模板)。 -- **缺失/待补**:创作者提现审核。 +- **缺失/待补**:暂无与提现审核相关缺口。 ## 4) 建议的下一步(按优先级) -1. **创作者提现审核**:补齐跨租户提现审核与财务联动入口。 -2. **内容/财务治理补齐**:评论/举报治理、钱包流水与异常排查能力。 +1. **内容/财务治理补齐**:评论/举报治理、钱包流水与异常排查能力。 diff --git a/frontend/superadmin/src/views/superadmin/Finance.vue b/frontend/superadmin/src/views/superadmin/Finance.vue index 2c4188f..d209a39 100644 --- a/frontend/superadmin/src/views/superadmin/Finance.vue +++ b/frontend/superadmin/src/views/superadmin/Finance.vue @@ -80,6 +80,27 @@ function formatCny(amountInCents) { return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(amount); } +function resolveWithdrawalSnapshot(order) { + const snapshot = order?.snapshot; + if (!snapshot || typeof snapshot !== 'object') return null; + const kind = snapshot.kind || snapshot.Kind; + const data = snapshot.data ?? snapshot.Data; + if (kind !== 'withdrawal') return null; + if (!data || typeof data !== 'object') return null; + return data; +} + +function formatWithdrawMethod(method) { + switch (method) { + case 'wallet': + return '钱包'; + case 'external': + return '外部打款'; + default: + return method || '-'; + } +} + function getStatusSeverity(value) { switch (value) { case 'paid': @@ -342,6 +363,18 @@ watch( {{ data.buyer?.id ?? '-' }} + + +