feat: add operator and business reference fields to tenant ledgers
- Added `operator_user_id`, `biz_ref_type`, and `biz_ref_id` fields to the TenantLedger model for enhanced auditing and traceability. - Updated the tenant ledgers query generation to include new fields. - Introduced new API endpoint for retrieving tenant ledger records with filtering options based on the new fields. - Enhanced Swagger documentation to reflect the new endpoint and its parameters. - Created DTOs for admin ledger filtering and item representation. - Implemented the admin ledger retrieval logic in the tenant service. - Added database migration scripts to introduce new fields and indexes for efficient querying.
This commit is contained in:
53
backend/app/http/tenant/dto/ledger_admin.go
Normal file
53
backend/app/http/tenant/dto/ledger_admin.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/database/models"
|
||||
"quyun/v2/pkg/consts"
|
||||
)
|
||||
|
||||
// AdminLedgerListFilter 定义“租户后台余额流水”查询条件。
|
||||
//
|
||||
// 设计目标:
|
||||
// - 用于审计/对账:可以按操作者(operator_user_id)检索敏感操作流水;
|
||||
// - 也可以按用户、订单、类型、业务引用快速定位流水集合。
|
||||
type AdminLedgerListFilter struct {
|
||||
// Pagination 分页参数(page/limit)。
|
||||
requests.Pagination `json:",inline" query:",inline"`
|
||||
|
||||
// OperatorUserID 按操作者用户ID过滤(可选)。
|
||||
// 典型场景:后台检索“某个管理员发起的充值/退款”等敏感操作流水。
|
||||
OperatorUserID *int64 `json:"operator_user_id,omitempty" query:"operator_user_id"`
|
||||
|
||||
// UserID 按余额账户归属用户ID过滤(可选)。
|
||||
// 典型场景:查看某个租户成员的资金变化全链路。
|
||||
UserID *int64 `json:"user_id,omitempty" query:"user_id"`
|
||||
|
||||
// Type 按流水类型过滤(可选)。
|
||||
Type *consts.TenantLedgerType `json:"type,omitempty" query:"type"`
|
||||
|
||||
// OrderID 按关联订单过滤(可选)。
|
||||
OrderID *int64 `json:"order_id,omitempty" query:"order_id"`
|
||||
|
||||
// BizRefType 按业务引用类型过滤(可选)。
|
||||
// 约定:当前业务写入为 "order";未来可扩展为 refund/topup 等。
|
||||
BizRefType *string `json:"biz_ref_type,omitempty" query:"biz_ref_type"`
|
||||
|
||||
// BizRefID 按业务引用ID过滤(可选)。
|
||||
BizRefID *int64 `json:"biz_ref_id,omitempty" query:"biz_ref_id"`
|
||||
|
||||
// CreatedAtFrom 创建时间起(可选)。
|
||||
CreatedAtFrom *time.Time `json:"created_at_from,omitempty" query:"created_at_from"`
|
||||
// CreatedAtTo 创建时间止(可选)。
|
||||
CreatedAtTo *time.Time `json:"created_at_to,omitempty" query:"created_at_to"`
|
||||
}
|
||||
|
||||
// AdminLedgerItem 返回一条余额流水(租户后台视角),并补充展示字段。
|
||||
type AdminLedgerItem struct {
|
||||
// Ledger 流水记录(租户内隔离)。
|
||||
Ledger *models.TenantLedger `json:"ledger"`
|
||||
// TypeDescription 流水类型中文说明(用于前端展示)。
|
||||
TypeDescription string `json:"type_description"`
|
||||
}
|
||||
57
backend/app/http/tenant/ledger_admin.go
Normal file
57
backend/app/http/tenant/ledger_admin.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package tenant
|
||||
|
||||
import (
|
||||
"quyun/v2/app/http/tenant/dto"
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/app/services"
|
||||
"quyun/v2/database/models"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ledgerAdmin provides tenant-admin ledger audit endpoints.
|
||||
//
|
||||
// @provider
|
||||
type ledgerAdmin struct{}
|
||||
|
||||
// adminLedgers
|
||||
//
|
||||
// @Summary 余额流水列表(租户管理/审计)
|
||||
// @Tags Tenant
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param tenantCode path string true "Tenant Code"
|
||||
// @Param filter query dto.AdminLedgerListFilter true "Filter"
|
||||
// @Success 200 {object} requests.Pager{items=dto.AdminLedgerItem}
|
||||
//
|
||||
// @Router /t/:tenantCode/v1/admin/ledgers [get]
|
||||
// @Bind tenant local key(tenant)
|
||||
// @Bind tenantUser local key(tenant_user)
|
||||
// @Bind filter query
|
||||
func (*ledgerAdmin) adminLedgers(
|
||||
ctx fiber.Ctx,
|
||||
tenant *models.Tenant,
|
||||
tenantUser *models.TenantUser,
|
||||
filter *dto.AdminLedgerListFilter,
|
||||
) (*requests.Pager, error) {
|
||||
if err := requireTenantAdmin(tenantUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if filter == nil {
|
||||
filter = &dto.AdminLedgerListFilter{}
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"tenant_id": tenant.ID,
|
||||
"user_id": tenantUser.UserID,
|
||||
"operator_user_id": filter.OperatorUserID,
|
||||
"target_user_id": filter.UserID,
|
||||
"type": filter.Type,
|
||||
"order_id": filter.OrderID,
|
||||
"biz_ref_type": filter.BizRefType,
|
||||
"biz_ref_id": filter.BizRefID,
|
||||
}).Info("tenant.admin.ledgers.list")
|
||||
|
||||
return services.Ledger.AdminLedgerPage(ctx.Context(), tenant.ID, filter)
|
||||
}
|
||||
@@ -24,6 +24,13 @@ func Provide(opts ...opt.Option) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func() (*ledgerAdmin, error) {
|
||||
obj := &ledgerAdmin{}
|
||||
|
||||
return obj, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func() (*me, error) {
|
||||
obj := &me{}
|
||||
|
||||
@@ -62,6 +69,7 @@ func Provide(opts ...opt.Option) error {
|
||||
if err := container.Container.Provide(func(
|
||||
content *content,
|
||||
contentAdmin *contentAdmin,
|
||||
ledgerAdmin *ledgerAdmin,
|
||||
me *me,
|
||||
mediaAssetAdmin *mediaAssetAdmin,
|
||||
middlewares *middlewares.Middlewares,
|
||||
@@ -75,6 +83,7 @@ func Provide(opts ...opt.Option) error {
|
||||
obj := &Routes{
|
||||
content: content,
|
||||
contentAdmin: contentAdmin,
|
||||
ledgerAdmin: ledgerAdmin,
|
||||
me: me,
|
||||
mediaAssetAdmin: mediaAssetAdmin,
|
||||
middlewares: middlewares,
|
||||
|
||||
@@ -26,6 +26,7 @@ type Routes struct {
|
||||
// Controller instances
|
||||
content *content
|
||||
contentAdmin *contentAdmin
|
||||
ledgerAdmin *ledgerAdmin
|
||||
me *me
|
||||
mediaAssetAdmin *mediaAssetAdmin
|
||||
order *order
|
||||
@@ -112,6 +113,14 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
PathParam[int64]("contentID"),
|
||||
Body[dto.ContentPriceUpsertForm]("form"),
|
||||
))
|
||||
// Register routes for controller: ledgerAdmin
|
||||
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/admin/ledgers -> ledgerAdmin.adminLedgers")
|
||||
router.Get("/t/:tenantCode/v1/admin/ledgers"[len(r.Path()):], DataFunc3(
|
||||
r.ledgerAdmin.adminLedgers,
|
||||
Local[*models.Tenant]("tenant"),
|
||||
Local[*models.TenantUser]("tenant_user"),
|
||||
Query[dto.AdminLedgerListFilter]("filter"),
|
||||
))
|
||||
// Register routes for controller: me
|
||||
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/me -> me.get")
|
||||
router.Get("/t/:tenantCode/v1/me"[len(r.Path()):], DataFunc3(
|
||||
|
||||
Reference in New Issue
Block a user