关闭公开注册并优化仪表盘
This commit is contained in:
@@ -35,26 +35,6 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
type registerReq struct {
|
||||
Username string `json:"username" binding:"required,min=3,max=32"`
|
||||
Password string `json:"password" binding:"required,min=6,max=64"`
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
var req registerReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误:用户名至少3位,密码至少6位")
|
||||
return
|
||||
}
|
||||
user, err := h.svc.Register(req.Username, req.Password, req.Nickname)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, user)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Profile(c *gin.Context) {
|
||||
user, err := h.svc.GetProfile(middleware.GetUserID(c))
|
||||
if err != nil {
|
||||
|
||||
@@ -47,7 +47,6 @@ func Setup(h *Handlers) *gin.Engine {
|
||||
api := r.Group("/api")
|
||||
{
|
||||
api.POST("/auth/login", h.Auth.Login)
|
||||
api.POST("/auth/register", h.Auth.Register)
|
||||
|
||||
// 源头侧接口:上游发货平台查询订单并回传发货结果。
|
||||
sourceOpen := api.Group("/open/v1")
|
||||
|
||||
@@ -46,37 +46,6 @@ func (s *AuthService) Login(username, password string) (*LoginResult, error) {
|
||||
return &LoginResult{Token: token, User: &user}, nil
|
||||
}
|
||||
|
||||
func (s *AuthService) Register(username, password, nickname string) (*model.User, error) {
|
||||
var count int64
|
||||
s.db.Model(&model.User{}).Where("username = ?", username).Count(&count)
|
||||
if count > 0 {
|
||||
return nil, errors.New("用户名已存在")
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := &model.User{
|
||||
Username: username,
|
||||
PasswordHash: string(hash),
|
||||
Nickname: nickname,
|
||||
Role: model.RoleMerchant,
|
||||
Status: 1,
|
||||
}
|
||||
if user.Nickname == "" {
|
||||
user.Nickname = username
|
||||
}
|
||||
if err := s.db.Create(user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.tenant != nil {
|
||||
if err := s.tenant.EnsureSelfMember(user.ID, model.MemberRoleOperator, user.Status); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *AuthService) GetProfile(userID uint) (*model.User, error) {
|
||||
var user model.User
|
||||
if err := s.db.First(&user, userID).Error; err != nil {
|
||||
|
||||
@@ -674,35 +674,189 @@ func orderCallbackData(order *model.FulfillmentOrder) map[string]interface{} {
|
||||
|
||||
// DashboardStats 仪表盘聚合指标。
|
||||
type DashboardStats struct {
|
||||
ProductCount int64 `json:"product_count"`
|
||||
MerchantCount int64 `json:"merchant_count"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
TotalSales int64 `json:"total_sales"`
|
||||
TotalFees int64 `json:"total_fees"`
|
||||
PendingOrderCount int64 `json:"pending_order_count"`
|
||||
Scope string `json:"scope"`
|
||||
CatalogProductCount int64 `json:"catalog_product_count"`
|
||||
ProductCount int64 `json:"product_count"`
|
||||
ActiveProductCount int64 `json:"active_product_count"`
|
||||
MerchantCount int64 `json:"merchant_count"`
|
||||
ActiveMerchantCount int64 `json:"active_merchant_count"`
|
||||
UserCount int64 `json:"user_count"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
TodayOrderCount int64 `json:"today_order_count"`
|
||||
TotalSales int64 `json:"total_sales"`
|
||||
TodaySales int64 `json:"today_sales"`
|
||||
TotalFees int64 `json:"total_fees"`
|
||||
TodayFees int64 `json:"today_fees"`
|
||||
PendingOrderCount int64 `json:"pending_order_count"`
|
||||
ProcessingOrderCount int64 `json:"processing_order_count"`
|
||||
SucceededOrderCount int64 `json:"succeeded_order_count"`
|
||||
FailedOrderCount int64 `json:"failed_order_count"`
|
||||
CancelledOrderCount int64 `json:"cancelled_order_count"`
|
||||
WalletAvailableBalance int64 `json:"wallet_available_balance"`
|
||||
WalletFrozenBalance int64 `json:"wallet_frozen_balance"`
|
||||
APIClientCount int64 `json:"api_client_count"`
|
||||
ActiveAPIClientCount int64 `json:"active_api_client_count"`
|
||||
CallbackSubscriptionCount int64 `json:"callback_subscription_count"`
|
||||
PendingCallbackCount int64 `json:"pending_callback_count"`
|
||||
FailedCallbackCount int64 `json:"failed_callback_count"`
|
||||
}
|
||||
|
||||
// Dashboard 汇总商户维度的商品、商户、订单与金额统计。
|
||||
type dashboardStatusCount struct {
|
||||
Status string
|
||||
Count int64
|
||||
}
|
||||
|
||||
// Dashboard 按角色汇总运营指标:平台管理员看全平台,商户账号看当前商户。
|
||||
func (s *FulfillmentService) Dashboard(merchantID uint, isPlatformAdmin bool) (*DashboardStats, error) {
|
||||
stats := &DashboardStats{}
|
||||
s.db.Model(&model.MerchantProduct{}).Where("merchant_id = ?", merchantID).Count(&stats.ProductCount)
|
||||
stats := &DashboardStats{Scope: "merchant"}
|
||||
if isPlatformAdmin {
|
||||
s.db.Model(&model.Merchant{}).Where("status = ?", model.MerchantStatusActive).Count(&stats.MerchantCount)
|
||||
stats.Scope = "platform"
|
||||
}
|
||||
now := time.Now()
|
||||
year, month, day := now.Date()
|
||||
todayStart := time.Date(year, month, day, 0, 0, 0, 0, now.Location())
|
||||
|
||||
productScope := func() *gorm.DB {
|
||||
tx := s.db.Model(&model.MerchantProduct{})
|
||||
if !isPlatformAdmin {
|
||||
tx = tx.Where("merchant_id = ?", merchantID)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
orderScope := func() *gorm.DB {
|
||||
tx := s.db.Model(&model.FulfillmentOrder{})
|
||||
if !isPlatformAdmin {
|
||||
tx = tx.Where("merchant_id = ?", merchantID)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
walletScope := func() *gorm.DB {
|
||||
tx := s.db.Model(&model.WalletAccount{})
|
||||
if !isPlatformAdmin {
|
||||
tx = tx.Where("merchant_id = ?", merchantID)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
apiClientScope := func() *gorm.DB {
|
||||
tx := s.db.Model(&model.APIClient{})
|
||||
if !isPlatformAdmin {
|
||||
tx = tx.Where("merchant_id = ?", merchantID)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
callbackScope := func() *gorm.DB {
|
||||
tx := s.db.Model(&model.CallbackSubscription{})
|
||||
if !isPlatformAdmin {
|
||||
tx = tx.Where("merchant_id = ?", merchantID)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
callbackDeliveryScope := func() *gorm.DB {
|
||||
tx := s.db.Model(&model.CallbackDelivery{})
|
||||
if !isPlatformAdmin {
|
||||
tx = tx.Where("merchant_id = ?", merchantID)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
if err := s.db.Model(&model.Product{}).Count(&stats.CatalogProductCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := productScope().Count(&stats.ProductCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := productScope().Where("status = ?", model.ProductStatusActive).Count(&stats.ActiveProductCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isPlatformAdmin {
|
||||
if err := s.db.Model(&model.Merchant{}).Count(&stats.MerchantCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.db.Model(&model.Merchant{}).Where("status = ?", model.MerchantStatusActive).Count(&stats.ActiveMerchantCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.db.Model(&model.User{}).Count(&stats.UserCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
stats.MerchantCount = 1
|
||||
if err := s.db.Model(&model.Merchant{}).Where("id = ?", merchantID).Count(&stats.MerchantCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.db.Model(&model.Merchant{}).Where("id = ? AND status = ?", merchantID, model.MerchantStatusActive).Count(&stats.ActiveMerchantCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.db.Model(&model.User{}).
|
||||
Joins("JOIN merchant_members ON merchant_members.user_id = users.id").
|
||||
Where("merchant_members.merchant_id = ?", merchantID).
|
||||
Count(&stats.UserCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.CatalogProductCount = stats.ProductCount
|
||||
}
|
||||
if err := orderScope().Count(&stats.OrderCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := orderScope().Where("created_at >= ?", todayStart).Count(&stats.TodayOrderCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := orderScope().Where("payment_status = ?", model.PaymentStatusPaid).
|
||||
Select("COALESCE(SUM(amount),0)").Scan(&stats.TotalSales).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := orderScope().Where("payment_status = ?", model.PaymentStatusPaid).
|
||||
Where("created_at >= ?", todayStart).
|
||||
Select("COALESCE(SUM(amount),0)").Scan(&stats.TodaySales).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := orderScope().Where("payment_status = ?", model.PaymentStatusPaid).
|
||||
Select("COALESCE(SUM(service_fee_amount),0)").Scan(&stats.TotalFees).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := orderScope().Where("payment_status = ?", model.PaymentStatusPaid).
|
||||
Where("created_at >= ?", todayStart).
|
||||
Select("COALESCE(SUM(service_fee_amount),0)").Scan(&stats.TodayFees).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var orderStatusCounts []dashboardStatusCount
|
||||
if err := orderScope().Select("fulfillment_status AS status, COUNT(*) AS count").
|
||||
Group("fulfillment_status").Scan(&orderStatusCounts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range orderStatusCounts {
|
||||
switch item.Status {
|
||||
case model.FulfillmentStatusPending:
|
||||
stats.PendingOrderCount = item.Count
|
||||
case model.FulfillmentStatusProcessing:
|
||||
stats.ProcessingOrderCount = item.Count
|
||||
case model.FulfillmentStatusSucceeded:
|
||||
stats.SucceededOrderCount = item.Count
|
||||
case model.FulfillmentStatusFailed:
|
||||
stats.FailedOrderCount = item.Count
|
||||
case model.FulfillmentStatusCancelled:
|
||||
stats.CancelledOrderCount = item.Count
|
||||
}
|
||||
}
|
||||
if err := walletScope().Select("COALESCE(SUM(available_balance),0)").Scan(&stats.WalletAvailableBalance).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := walletScope().Select("COALESCE(SUM(frozen_balance),0)").Scan(&stats.WalletFrozenBalance).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apiClientScope().Count(&stats.APIClientCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apiClientScope().Where("status = ?", model.APIClientStatusActive).Count(&stats.ActiveAPIClientCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := callbackScope().Count(&stats.CallbackSubscriptionCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := callbackDeliveryScope().Where("status = ?", model.CallbackDeliveryPending).Count(&stats.PendingCallbackCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := callbackDeliveryScope().Where("status = ?", model.CallbackDeliveryFailed).Count(&stats.FailedCallbackCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.db.Model(&model.FulfillmentOrder{}).Where("merchant_id = ?", merchantID).Count(&stats.OrderCount)
|
||||
s.db.Model(&model.FulfillmentOrder{}).
|
||||
Where("merchant_id = ? AND fulfillment_status = ?", merchantID, model.FulfillmentStatusPending).
|
||||
Count(&stats.PendingOrderCount)
|
||||
s.db.Model(&model.FulfillmentOrder{}).
|
||||
Where("merchant_id = ?", merchantID).
|
||||
Where("payment_status = ?", model.PaymentStatusPaid).
|
||||
Select("COALESCE(SUM(amount),0)").Scan(&stats.TotalSales)
|
||||
s.db.Model(&model.FulfillmentOrder{}).
|
||||
Where("merchant_id = ?", merchantID).
|
||||
Where("payment_status = ?", model.PaymentStatusPaid).
|
||||
Select("COALESCE(SUM(service_fee_amount),0)").Scan(&stats.TotalFees)
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,64 @@ func TestFulfillmentCreateOrderDebitsWalletAndIsIdempotent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDashboardScopesPlatformAndMerchantData(t *testing.T) {
|
||||
db := newServiceTestDB(t)
|
||||
merchantA, productA := seedFulfillmentMerchant(t, db, "dashboard-a", 1000, 5, 100)
|
||||
merchantB, productB := seedFulfillmentMerchant(t, db, "dashboard-b", 2000, 5, 300)
|
||||
svc := NewFulfillmentService(db, nil)
|
||||
|
||||
if _, err := svc.CreateOrder(CreateFulfillmentOrderInput{
|
||||
MerchantID: merchantA,
|
||||
APIClientID: 31,
|
||||
ClientOrderNo: "dashboard-a-001",
|
||||
SKU: productA.SKU,
|
||||
}); err != nil {
|
||||
t.Fatalf("create order a: %v", err)
|
||||
}
|
||||
if _, err := svc.CreateOrder(CreateFulfillmentOrderInput{
|
||||
MerchantID: merchantB,
|
||||
APIClientID: 32,
|
||||
ClientOrderNo: "dashboard-b-001",
|
||||
SKU: productB.SKU,
|
||||
}); err != nil {
|
||||
t.Fatalf("create order b: %v", err)
|
||||
}
|
||||
|
||||
merchantStats, err := svc.Dashboard(merchantA, false)
|
||||
if err != nil {
|
||||
t.Fatalf("merchant dashboard: %v", err)
|
||||
}
|
||||
if merchantStats.Scope != "merchant" {
|
||||
t.Fatalf("expected merchant scope, got %s", merchantStats.Scope)
|
||||
}
|
||||
if merchantStats.ProductCount != 1 || merchantStats.ActiveProductCount != 1 {
|
||||
t.Fatalf("merchant product stats should only include current merchant, got %+v", merchantStats)
|
||||
}
|
||||
if merchantStats.OrderCount != 1 || merchantStats.PendingOrderCount != 1 || merchantStats.TotalSales != 100 {
|
||||
t.Fatalf("merchant order stats should only include current merchant, got %+v", merchantStats)
|
||||
}
|
||||
if merchantStats.WalletAvailableBalance != 900 {
|
||||
t.Fatalf("merchant wallet should only include current merchant, got %d", merchantStats.WalletAvailableBalance)
|
||||
}
|
||||
|
||||
platformStats, err := svc.Dashboard(merchantA, true)
|
||||
if err != nil {
|
||||
t.Fatalf("platform dashboard: %v", err)
|
||||
}
|
||||
if platformStats.Scope != "platform" {
|
||||
t.Fatalf("expected platform scope, got %s", platformStats.Scope)
|
||||
}
|
||||
if platformStats.OrderCount != 2 || platformStats.PendingOrderCount != 2 || platformStats.TotalSales != 400 {
|
||||
t.Fatalf("platform order stats should include all merchants, got %+v", platformStats)
|
||||
}
|
||||
if platformStats.WalletAvailableBalance < 2600 {
|
||||
t.Fatalf("platform wallet should include all merchant wallets, got %d", platformStats.WalletAvailableBalance)
|
||||
}
|
||||
if platformStats.ProductCount <= merchantStats.ProductCount {
|
||||
t.Fatalf("platform product stats should be broader than merchant stats, got platform=%d merchant=%d", platformStats.ProductCount, merchantStats.ProductCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFulfillmentCreateOrderAppliesMerchantFeeRate(t *testing.T) {
|
||||
db := newServiceTestDB(t)
|
||||
merchantID, product := seedFulfillmentMerchant(t, db, "merchant-fee-rate", 1000, 5, 200)
|
||||
|
||||
Reference in New Issue
Block a user