Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server
This commit is contained in:
@@ -9,6 +9,36 @@ type SubPortalRegisterCommand struct {
|
||||
InviteToken string `json:"invite_token" binding:"required"`
|
||||
}
|
||||
|
||||
// ListChildApiCallsCommand 下属 API 调用记录查询
|
||||
type ListChildApiCallsCommand struct {
|
||||
ParentUserID string
|
||||
ChildUserID string `form:"child_user_id" binding:"required"`
|
||||
Page int `form:"page"`
|
||||
PageSize int `form:"page_size"`
|
||||
TransactionID string `form:"transaction_id"`
|
||||
ProductName string `form:"product_name"`
|
||||
Status string `form:"status"`
|
||||
StartTime string `form:"start_time"`
|
||||
EndTime string `form:"end_time"`
|
||||
}
|
||||
|
||||
// ListSubordinatesCommand 下属列表查询
|
||||
type ListSubordinatesCommand struct {
|
||||
ParentUserID string
|
||||
Page int `form:"page"`
|
||||
PageSize int `form:"page_size"`
|
||||
Remark string `form:"remark"`
|
||||
Phone string `form:"phone"`
|
||||
CompanyName string `form:"company_name"`
|
||||
}
|
||||
|
||||
// UpdateSubordinateRemarkCommand 更新下属备注
|
||||
type UpdateSubordinateRemarkCommand struct {
|
||||
ParentUserID string
|
||||
ChildUserID string `json:"child_user_id" binding:"required"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// CreateInvitationCommand 主账号创建邀请
|
||||
type CreateInvitationCommand struct {
|
||||
ParentUserID string
|
||||
|
||||
@@ -10,15 +10,24 @@ type CreateInvitationResponse struct {
|
||||
InvitationID string `json:"invitation_id"`
|
||||
}
|
||||
|
||||
// SubordinateProductQuotaItem 下属产品额度
|
||||
type SubordinateProductQuotaItem struct {
|
||||
ProductID string `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
AvailableQuota int64 `json:"available_quota"`
|
||||
}
|
||||
|
||||
// SubordinateListItem 下属一条
|
||||
type SubordinateListItem struct {
|
||||
ChildUserID string `json:"child_user_id"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
LinkID string `json:"link_id"`
|
||||
RegisteredAt time.Time `json:"registered_at"`
|
||||
CompanyName string `json:"company_name"`
|
||||
IsCertified bool `json:"is_certified"`
|
||||
Balance string `json:"balance"`
|
||||
ChildUserID string `json:"child_user_id"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
LinkID string `json:"link_id"`
|
||||
Remark string `json:"remark"`
|
||||
RegisteredAt time.Time `json:"registered_at"`
|
||||
CompanyName string `json:"company_name"`
|
||||
IsCertified bool `json:"is_certified"`
|
||||
Balance string `json:"balance"`
|
||||
ProductQuotas []SubordinateProductQuotaItem `json:"product_quotas"`
|
||||
}
|
||||
|
||||
// SubordinateListResponse 列表
|
||||
|
||||
@@ -3,6 +3,7 @@ package subordinate
|
||||
import (
|
||||
"context"
|
||||
|
||||
api_dto "tyapi-server/internal/application/api/dto"
|
||||
"tyapi-server/internal/application/subordinate/dto/commands"
|
||||
"tyapi-server/internal/application/subordinate/dto/responses"
|
||||
)
|
||||
@@ -11,7 +12,8 @@ import (
|
||||
type SubordinateApplicationService interface {
|
||||
RegisterSubPortal(ctx context.Context, cmd *commands.SubPortalRegisterCommand) (*responses.SubPortalRegisterResponse, error)
|
||||
CreateInvitation(ctx context.Context, cmd *commands.CreateInvitationCommand) (*responses.CreateInvitationResponse, error)
|
||||
ListMySubordinates(ctx context.Context, parentUserID string, page, pageSize int) (*responses.SubordinateListResponse, error)
|
||||
ListMySubordinates(ctx context.Context, cmd *commands.ListSubordinatesCommand) (*responses.SubordinateListResponse, error)
|
||||
UpdateSubordinateRemark(ctx context.Context, cmd *commands.UpdateSubordinateRemarkCommand) error
|
||||
AllocateToChild(ctx context.Context, cmd *commands.AllocateToChildCommand) error
|
||||
ListChildAllocations(ctx context.Context, cmd *commands.ListChildAllocationsCommand) (*responses.ChildAllocationListResponse, error)
|
||||
AssignChildSubscription(ctx context.Context, cmd *commands.AssignChildSubscriptionCommand) error
|
||||
@@ -20,5 +22,6 @@ type SubordinateApplicationService interface {
|
||||
PurchaseChildQuota(ctx context.Context, cmd *commands.PurchaseChildQuotaCommand) error
|
||||
ListChildQuotaPurchases(ctx context.Context, cmd *commands.ListChildQuotaPurchasesCommand) (*responses.ChildQuotaPurchaseListResponse, error)
|
||||
ListChildQuotaAccounts(ctx context.Context, cmd *commands.ListChildQuotaAccountsCommand) ([]responses.ChildQuotaAccountItem, error)
|
||||
ListChildApiCalls(ctx context.Context, cmd *commands.ListChildApiCallsCommand) (*api_dto.ApiCallListResponse, error)
|
||||
ListMyQuotaAccounts(ctx context.Context, userID string) ([]responses.ChildQuotaAccountItem, error)
|
||||
}
|
||||
|
||||
@@ -11,18 +11,22 @@ import (
|
||||
"github.com/shopspring/decimal"
|
||||
"go.uber.org/zap"
|
||||
|
||||
api_app "tyapi-server/internal/application/api"
|
||||
api_dto "tyapi-server/internal/application/api/dto"
|
||||
"tyapi-server/internal/application/subordinate/dto/commands"
|
||||
"tyapi-server/internal/application/subordinate/dto/responses"
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/domains/finance/repositories"
|
||||
productentities "tyapi-server/internal/domains/product/entities"
|
||||
product_service "tyapi-server/internal/domains/product/services"
|
||||
product_repositories "tyapi-server/internal/domains/product/repositories"
|
||||
subentities "tyapi-server/internal/domains/subordinate/entities"
|
||||
subrepositories "tyapi-server/internal/domains/subordinate/repositories"
|
||||
user_entities "tyapi-server/internal/domains/user/entities"
|
||||
user_repositories "tyapi-server/internal/domains/user/repositories"
|
||||
domain_user_services "tyapi-server/internal/domains/user/services"
|
||||
"tyapi-server/internal/shared/database"
|
||||
shared_interfaces "tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// SubordinateApplicationServiceImpl 实现
|
||||
@@ -30,11 +34,13 @@ type SubordinateApplicationServiceImpl struct {
|
||||
subRepo subrepositories.SubordinateRepository
|
||||
userAgg domain_user_services.UserAggregateService
|
||||
smsService *domain_user_services.SMSCodeService
|
||||
productSub *product_service.ProductSubscriptionService
|
||||
productSub *product_service.ProductSubscriptionService
|
||||
productRepo product_repositories.ProductRepository
|
||||
cfg *config.Config
|
||||
txm *database.TransactionManager
|
||||
walletRepo repositories.WalletRepository
|
||||
userRepo user_repositories.UserRepository
|
||||
apiApp api_app.ApiApplicationService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
@@ -44,10 +50,12 @@ func NewSubordinateApplicationService(
|
||||
userAgg domain_user_services.UserAggregateService,
|
||||
smsService *domain_user_services.SMSCodeService,
|
||||
productSub *product_service.ProductSubscriptionService,
|
||||
productRepo product_repositories.ProductRepository,
|
||||
cfg *config.Config,
|
||||
txm *database.TransactionManager,
|
||||
walletRepo repositories.WalletRepository,
|
||||
userRepo user_repositories.UserRepository,
|
||||
apiApp api_app.ApiApplicationService,
|
||||
logger *zap.Logger,
|
||||
) SubordinateApplicationService {
|
||||
return &SubordinateApplicationServiceImpl{
|
||||
@@ -55,10 +63,12 @@ func NewSubordinateApplicationService(
|
||||
userAgg: userAgg,
|
||||
smsService: smsService,
|
||||
productSub: productSub,
|
||||
productRepo: productRepo,
|
||||
cfg: cfg,
|
||||
txm: txm,
|
||||
walletRepo: walletRepo,
|
||||
userRepo: userRepo,
|
||||
apiApp: apiApp,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
@@ -176,7 +186,9 @@ func (s *SubordinateApplicationServiceImpl) buildInvitationResponse(inv *subenti
|
||||
}
|
||||
|
||||
// ListMySubordinates 主账号的下属
|
||||
func (s *SubordinateApplicationServiceImpl) ListMySubordinates(ctx context.Context, parentUserID string, page, pageSize int) (*responses.SubordinateListResponse, error) {
|
||||
func (s *SubordinateApplicationServiceImpl) ListMySubordinates(ctx context.Context, cmd *commands.ListSubordinatesCommand) (*responses.SubordinateListResponse, error) {
|
||||
page := cmd.Page
|
||||
pageSize := cmd.PageSize
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
@@ -184,10 +196,58 @@ func (s *SubordinateApplicationServiceImpl) ListMySubordinates(ctx context.Conte
|
||||
pageSize = 20
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
links, total, err := s.subRepo.ListChildrenByParent(ctx, parentUserID, pageSize, offset)
|
||||
filter := subrepositories.SubordinateListFilter{
|
||||
Remark: strings.TrimSpace(cmd.Remark),
|
||||
Phone: strings.TrimSpace(cmd.Phone),
|
||||
CompanyName: strings.TrimSpace(cmd.CompanyName),
|
||||
}
|
||||
links, total, err := s.subRepo.ListChildrenByParent(ctx, cmd.ParentUserID, filter, pageSize, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
childIDs := make([]string, 0, len(links))
|
||||
for _, ln := range links {
|
||||
childIDs = append(childIDs, ln.ChildUserID)
|
||||
}
|
||||
|
||||
quotaByUser := make(map[string][]*subentities.UserProductQuotaAccount)
|
||||
if len(childIDs) > 0 {
|
||||
accounts, quotaErr := s.subRepo.ListQuotaAccountsByUserIDs(ctx, childIDs)
|
||||
if quotaErr != nil {
|
||||
return nil, quotaErr
|
||||
}
|
||||
for _, account := range accounts {
|
||||
quotaByUser[account.UserID] = append(quotaByUser[account.UserID], account)
|
||||
}
|
||||
}
|
||||
|
||||
productNameMap := make(map[string]string)
|
||||
if s.productRepo != nil {
|
||||
productIDSet := make(map[string]struct{})
|
||||
for _, accounts := range quotaByUser {
|
||||
for _, account := range accounts {
|
||||
productIDSet[account.ProductID] = struct{}{}
|
||||
}
|
||||
}
|
||||
if len(productIDSet) > 0 {
|
||||
productIDs := make([]string, 0, len(productIDSet))
|
||||
for id := range productIDSet {
|
||||
productIDs = append(productIDs, id)
|
||||
}
|
||||
products, productErr := s.productRepo.FindProductsByIDs(ctx, productIDs)
|
||||
if productErr != nil {
|
||||
s.logger.Warn("批量获取产品名称失败", zap.Error(productErr))
|
||||
} else {
|
||||
for _, product := range products {
|
||||
if product != nil {
|
||||
productNameMap[product.ID] = product.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]responses.SubordinateListItem, 0, len(links))
|
||||
for _, ln := range links {
|
||||
phone := ""
|
||||
@@ -209,19 +269,52 @@ func (s *SubordinateApplicationServiceImpl) ListMySubordinates(ctx context.Conte
|
||||
if w, e := s.walletRepo.GetByUserID(ctx, ln.ChildUserID); e == nil && w != nil {
|
||||
balance = w.Balance.StringFixed(2)
|
||||
}
|
||||
|
||||
productQuotas := make([]responses.SubordinateProductQuotaItem, 0)
|
||||
for _, account := range quotaByUser[ln.ChildUserID] {
|
||||
name := productNameMap[account.ProductID]
|
||||
if name == "" {
|
||||
name = account.ProductID
|
||||
}
|
||||
productQuotas = append(productQuotas, responses.SubordinateProductQuotaItem{
|
||||
ProductID: account.ProductID,
|
||||
ProductName: name,
|
||||
AvailableQuota: account.AvailableQuota,
|
||||
})
|
||||
}
|
||||
|
||||
items = append(items, responses.SubordinateListItem{
|
||||
ChildUserID: ln.ChildUserID,
|
||||
Phone: phone,
|
||||
LinkID: ln.ID,
|
||||
RegisteredAt: registeredAt,
|
||||
CompanyName: companyName,
|
||||
IsCertified: isCertified,
|
||||
Balance: balance,
|
||||
ChildUserID: ln.ChildUserID,
|
||||
Phone: phone,
|
||||
LinkID: ln.ID,
|
||||
Remark: ln.Remark,
|
||||
RegisteredAt: registeredAt,
|
||||
CompanyName: companyName,
|
||||
IsCertified: isCertified,
|
||||
Balance: balance,
|
||||
ProductQuotas: productQuotas,
|
||||
})
|
||||
}
|
||||
return &responses.SubordinateListResponse{Total: total, Items: items}, nil
|
||||
}
|
||||
|
||||
// UpdateSubordinateRemark 更新下属备注
|
||||
func (s *SubordinateApplicationServiceImpl) UpdateSubordinateRemark(ctx context.Context, cmd *commands.UpdateSubordinateRemarkCommand) error {
|
||||
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
|
||||
return fmt.Errorf("该用户不是您的有效下属")
|
||||
}
|
||||
remark := strings.TrimSpace(cmd.Remark)
|
||||
if len([]rune(remark)) > 255 {
|
||||
return fmt.Errorf("备注不能超过255个字符")
|
||||
}
|
||||
lnk.Remark = remark
|
||||
return s.subRepo.UpdateLink(ctx, lnk)
|
||||
}
|
||||
|
||||
// AllocateToChild 划款
|
||||
func (s *SubordinateApplicationServiceImpl) AllocateToChild(ctx context.Context, cmd *commands.AllocateToChildCommand) error {
|
||||
amount, err := decimal.NewFromString(strings.TrimSpace(cmd.Amount))
|
||||
@@ -604,6 +697,57 @@ func (s *SubordinateApplicationServiceImpl) ListChildQuotaAccounts(ctx context.C
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// ListChildApiCalls 主账号查看下属 API 调用记录
|
||||
func (s *SubordinateApplicationServiceImpl) ListChildApiCalls(ctx context.Context, cmd *commands.ListChildApiCallsCommand) (*api_dto.ApiCallListResponse, error) {
|
||||
if s.apiApp == nil {
|
||||
return nil, fmt.Errorf("API 服务未初始化")
|
||||
}
|
||||
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
|
||||
return nil, fmt.Errorf("该用户不是您的有效下属")
|
||||
}
|
||||
|
||||
page := cmd.Page
|
||||
pageSize := cmd.PageSize
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
filters := make(map[string]interface{})
|
||||
if strings.TrimSpace(cmd.TransactionID) != "" {
|
||||
filters["transaction_id"] = strings.TrimSpace(cmd.TransactionID)
|
||||
}
|
||||
if strings.TrimSpace(cmd.ProductName) != "" {
|
||||
filters["product_name"] = strings.TrimSpace(cmd.ProductName)
|
||||
}
|
||||
if strings.TrimSpace(cmd.Status) != "" {
|
||||
filters["status"] = strings.TrimSpace(cmd.Status)
|
||||
}
|
||||
if strings.TrimSpace(cmd.StartTime) != "" {
|
||||
if t, parseErr := time.Parse("2006-01-02 15:04:05", strings.TrimSpace(cmd.StartTime)); parseErr == nil {
|
||||
filters["start_time"] = t
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(cmd.EndTime) != "" {
|
||||
if t, parseErr := time.Parse("2006-01-02 15:04:05", strings.TrimSpace(cmd.EndTime)); parseErr == nil {
|
||||
filters["end_time"] = t
|
||||
}
|
||||
}
|
||||
|
||||
return s.apiApp.GetUserApiCalls(ctx, cmd.ChildUserID, filters, shared_interfaces.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Sort: "created_at",
|
||||
Order: "desc",
|
||||
})
|
||||
}
|
||||
|
||||
// ListMyQuotaAccounts 查询当前用户额度账户(通用能力,适配所有用户)
|
||||
func (s *SubordinateApplicationServiceImpl) ListMyQuotaAccounts(ctx context.Context, userID string) ([]responses.ChildQuotaAccountItem, error) {
|
||||
accounts, err := s.subRepo.ListQuotaAccountsByUser(ctx, userID)
|
||||
|
||||
@@ -21,6 +21,7 @@ type UserSubordinateLink struct {
|
||||
ParentUserID string `gorm:"type:varchar(36);not null;index:idx_parent,priority:1" json:"parent_user_id" comment:"主账号用户ID"`
|
||||
ChildUserID string `gorm:"type:varchar(36);not null;uniqueIndex" json:"child_user_id" comment:"子账号用户ID(唯一)"`
|
||||
InvitationID *string `gorm:"type:varchar(36);index" json:"invitation_id,omitempty" comment:"关联的邀请ID"`
|
||||
Remark string `gorm:"type:varchar(255)" json:"remark" comment:"主账号备注"`
|
||||
Status LinkStatus `gorm:"type:varchar(20);not null;default:active" json:"status" comment:"状态"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package repositories
|
||||
|
||||
// SubordinateListFilter 下属列表筛选
|
||||
type SubordinateListFilter struct {
|
||||
Remark string
|
||||
Phone string
|
||||
CompanyName string
|
||||
}
|
||||
@@ -21,7 +21,8 @@ type SubordinateRepository interface {
|
||||
CreateLink(ctx context.Context, link *entities.UserSubordinateLink) error
|
||||
FindLinkByChildUserID(ctx context.Context, childUserID string) (*entities.UserSubordinateLink, error)
|
||||
FindLinkByParentAndChild(ctx context.Context, parentUserID, childUserID string) (*entities.UserSubordinateLink, error)
|
||||
ListChildrenByParent(ctx context.Context, parentUserID string, limit, offset int) ([]*entities.UserSubordinateLink, int64, error)
|
||||
ListChildrenByParent(ctx context.Context, parentUserID string, filter SubordinateListFilter, limit, offset int) ([]*entities.UserSubordinateLink, int64, error)
|
||||
ListQuotaAccountsByUserIDs(ctx context.Context, userIDs []string) ([]*entities.UserProductQuotaAccount, error)
|
||||
UpdateLink(ctx context.Context, link *entities.UserSubordinateLink) error
|
||||
// 是否存在子账号关系(任意子账号)
|
||||
IsUserSubordinate(ctx context.Context, userID string) (bool, error)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
@@ -152,15 +153,31 @@ func (r *GormSubordinateRepository) FindLinkByParentAndChild(ctx context.Context
|
||||
return &l, nil
|
||||
}
|
||||
|
||||
// ListChildrenByParent 列出下属
|
||||
func (r *GormSubordinateRepository) ListChildrenByParent(ctx context.Context, parentUserID string, limit, offset int) ([]*entities.UserSubordinateLink, int64, error) {
|
||||
// ListChildrenByParent 列出下属(支持备注/手机号/公司名模糊筛选)
|
||||
func (r *GormSubordinateRepository) ListChildrenByParent(ctx context.Context, parentUserID string, filter repositories.SubordinateListFilter, limit, offset int) ([]*entities.UserSubordinateLink, int64, error) {
|
||||
var list []entities.UserSubordinateLink
|
||||
var total int64
|
||||
q := r.withCtx(ctx).Model(&entities.UserSubordinateLink{}).Where("parent_user_id = ? AND status = ?", parentUserID, entities.LinkStatusActive)
|
||||
q := r.withCtx(ctx).Model(&entities.UserSubordinateLink{}).
|
||||
Where("user_subordinate_links.parent_user_id = ? AND user_subordinate_links.status = ?", parentUserID, entities.LinkStatusActive)
|
||||
|
||||
if strings.TrimSpace(filter.Remark) != "" {
|
||||
q = q.Where("user_subordinate_links.remark LIKE ?", "%"+strings.TrimSpace(filter.Remark)+"%")
|
||||
}
|
||||
if strings.TrimSpace(filter.Phone) != "" || strings.TrimSpace(filter.CompanyName) != "" {
|
||||
q = q.Joins("JOIN users ON users.id = user_subordinate_links.child_user_id AND users.deleted_at IS NULL")
|
||||
if strings.TrimSpace(filter.Phone) != "" {
|
||||
q = q.Where("users.phone LIKE ?", "%"+strings.TrimSpace(filter.Phone)+"%")
|
||||
}
|
||||
if strings.TrimSpace(filter.CompanyName) != "" {
|
||||
q = q.Joins("LEFT JOIN enterprise_infos ON enterprise_infos.user_id = users.id AND enterprise_infos.deleted_at IS NULL").
|
||||
Where("enterprise_infos.company_name LIKE ?", "%"+strings.TrimSpace(filter.CompanyName)+"%")
|
||||
}
|
||||
}
|
||||
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := q.Order("created_at DESC").Limit(limit).Offset(offset).Find(&list).Error; err != nil {
|
||||
if err := q.Order("user_subordinate_links.created_at DESC").Limit(limit).Offset(offset).Find(&list).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*entities.UserSubordinateLink, len(list))
|
||||
@@ -185,6 +202,22 @@ func (r *GormSubordinateRepository) IsUserSubordinate(ctx context.Context, userI
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
// ListQuotaAccountsByUserIDs 批量查询用户产品额度账户
|
||||
func (r *GormSubordinateRepository) ListQuotaAccountsByUserIDs(ctx context.Context, userIDs []string) ([]*entities.UserProductQuotaAccount, error) {
|
||||
if len(userIDs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var list []entities.UserProductQuotaAccount
|
||||
if err := r.withCtx(ctx).Where("user_id IN ?", userIDs).Order("user_id ASC, updated_at DESC").Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]*entities.UserProductQuotaAccount, len(list))
|
||||
for i := range list {
|
||||
out[i] = &list[i]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// CreateWalletAllocation 记划拨
|
||||
func (r *GormSubordinateRepository) CreateWalletAllocation(ctx context.Context, a *entities.SubordinateWalletAllocation) error {
|
||||
// 幂等:同 business_ref 不重复
|
||||
|
||||
@@ -76,7 +76,14 @@ func (h *SubordinateHandler) ListSubordinates(c *gin.Context) {
|
||||
}
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
res, err := h.app.ListMySubordinates(c.Request.Context(), parentID, page, size)
|
||||
res, err := h.app.ListMySubordinates(c.Request.Context(), &commands.ListSubordinatesCommand{
|
||||
ParentUserID: parentID,
|
||||
Page: page,
|
||||
PageSize: size,
|
||||
Remark: c.Query("remark"),
|
||||
Phone: c.Query("phone"),
|
||||
CompanyName: c.Query("company_name"),
|
||||
})
|
||||
if err != nil {
|
||||
h.logger.Error("获取下属列表失败", zap.Error(err))
|
||||
h.response.InternalError(c, "获取下属列表失败")
|
||||
@@ -85,6 +92,26 @@ func (h *SubordinateHandler) ListSubordinates(c *gin.Context) {
|
||||
h.response.Success(c, res, "获取成功")
|
||||
}
|
||||
|
||||
// UpdateSubordinateRemark 更新下属备注
|
||||
func (h *SubordinateHandler) UpdateSubordinateRemark(c *gin.Context) {
|
||||
parentID := c.GetString("user_id")
|
||||
if parentID == "" {
|
||||
h.response.Unauthorized(c, "未登录")
|
||||
return
|
||||
}
|
||||
var cmd commands.UpdateSubordinateRemarkCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
cmd.ParentUserID = parentID
|
||||
if err := h.app.UpdateSubordinateRemark(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("更新下属备注失败", zap.Error(err))
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
h.response.Success(c, nil, "备注已更新")
|
||||
}
|
||||
|
||||
// Allocate 划款
|
||||
func (h *SubordinateHandler) Allocate(c *gin.Context) {
|
||||
parentID := c.GetString("user_id")
|
||||
@@ -282,6 +309,39 @@ func (h *SubordinateHandler) ListChildQuotaAccounts(c *gin.Context) {
|
||||
h.response.Success(c, res, "获取成功")
|
||||
}
|
||||
|
||||
// ListChildApiCalls 下属 API 调用记录
|
||||
func (h *SubordinateHandler) ListChildApiCalls(c *gin.Context) {
|
||||
parentID := c.GetString("user_id")
|
||||
if parentID == "" {
|
||||
h.response.Unauthorized(c, "未登录")
|
||||
return
|
||||
}
|
||||
childID := c.Query("child_user_id")
|
||||
if childID == "" {
|
||||
h.response.BadRequest(c, "child_user_id 不能为空")
|
||||
return
|
||||
}
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
res, err := h.app.ListChildApiCalls(c.Request.Context(), &commands.ListChildApiCallsCommand{
|
||||
ParentUserID: parentID,
|
||||
ChildUserID: childID,
|
||||
Page: page,
|
||||
PageSize: size,
|
||||
TransactionID: c.Query("transaction_id"),
|
||||
ProductName: c.Query("product_name"),
|
||||
Status: c.Query("status"),
|
||||
StartTime: c.Query("start_time"),
|
||||
EndTime: c.Query("end_time"),
|
||||
})
|
||||
if err != nil {
|
||||
h.logger.Error("获取下属调用记录失败", zap.Error(err))
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
h.response.Success(c, res, "获取成功")
|
||||
}
|
||||
|
||||
// ListMyQuotaAccounts 当前登录用户额度账户
|
||||
func (h *SubordinateHandler) ListMyQuotaAccounts(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
|
||||
@@ -35,6 +35,7 @@ func (r *SubordinateRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
{
|
||||
sub.POST("/invitations", r.handler.CreateInvitation)
|
||||
sub.GET("/subordinates", r.handler.ListSubordinates)
|
||||
sub.PATCH("/subordinates/remark", r.handler.UpdateSubordinateRemark)
|
||||
sub.POST("/allocate", r.handler.Allocate)
|
||||
sub.GET("/allocations", r.handler.ListAllocations)
|
||||
sub.POST("/assign-subscription", r.handler.AssignSubscription)
|
||||
@@ -43,6 +44,7 @@ func (r *SubordinateRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
sub.POST("/purchase-quota", r.handler.PurchaseQuota)
|
||||
sub.GET("/quota-purchases", r.handler.ListQuotaPurchases)
|
||||
sub.GET("/child-quotas", r.handler.ListChildQuotaAccounts)
|
||||
sub.GET("/child-api-calls", r.handler.ListChildApiCalls)
|
||||
sub.GET("/my-quotas", r.handler.ListMyQuotaAccounts)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>企业全景报告</title>
|
||||
<title>企业综合报告</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
@@ -1001,7 +1001,6 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -1014,7 +1013,7 @@
|
||||
<header class="header">
|
||||
<div class="header-inner">
|
||||
<div class="header-main">
|
||||
<h1 class="report-title">企业全景报告</h1>
|
||||
<h1 class="report-title">企业综合报告</h1>
|
||||
<div class="subtitle">
|
||||
<span id="entName">企业名称</span>
|
||||
</div>
|
||||
@@ -3578,7 +3577,7 @@
|
||||
return resp.blob();
|
||||
})
|
||||
.then(function (blob) {
|
||||
var fileName = "企业全景报告.pdf";
|
||||
var fileName = "企业综合报告.pdf";
|
||||
if (
|
||||
reportData &&
|
||||
reportData.entName &&
|
||||
@@ -3586,7 +3585,7 @@
|
||||
) {
|
||||
fileName =
|
||||
reportData.entName +
|
||||
"_企业全景报告.pdf";
|
||||
"_企业综合报告.pdf";
|
||||
}
|
||||
var url = window.URL.createObjectURL(blob);
|
||||
var a = document.createElement("a");
|
||||
|
||||
Reference in New Issue
Block a user