All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m22s
- 移动 17 个 DTO 文件到 internal/model/dto/ 目录 - 更新所有 DTO 文件的 package 声明从 model 改为 dto - 更新所有引用文件的 import 和类型引用 - Handler 层:admin 和 h5 所有处理器 - Service 层:所有业务服务 - Routes 层:所有路由定义 - Tests 层:单元测试和集成测试 - 清理未使用的 import 语句 - 验证:项目构建成功,测试编译通过,LSP 无错误
429 lines
12 KiB
Go
429 lines
12 KiB
Go
package shop_commission
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
|
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
|
"github.com/break/junhong_cmp_fiber/internal/store"
|
|
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
)
|
|
|
|
type Service struct {
|
|
shopStore *postgres.ShopStore
|
|
accountStore *postgres.AccountStore
|
|
walletStore *postgres.WalletStore
|
|
commissionWithdrawalReqStore *postgres.CommissionWithdrawalRequestStore
|
|
commissionRecordStore *postgres.CommissionRecordStore
|
|
}
|
|
|
|
func New(
|
|
shopStore *postgres.ShopStore,
|
|
accountStore *postgres.AccountStore,
|
|
walletStore *postgres.WalletStore,
|
|
commissionWithdrawalReqStore *postgres.CommissionWithdrawalRequestStore,
|
|
commissionRecordStore *postgres.CommissionRecordStore,
|
|
) *Service {
|
|
return &Service{
|
|
shopStore: shopStore,
|
|
accountStore: accountStore,
|
|
walletStore: walletStore,
|
|
commissionWithdrawalReqStore: commissionWithdrawalReqStore,
|
|
commissionRecordStore: commissionRecordStore,
|
|
}
|
|
}
|
|
|
|
func (s *Service) ListShopCommissionSummary(ctx context.Context, req *dto.ShopCommissionSummaryListReq) (*dto.ShopCommissionSummaryPageResult, error) {
|
|
opts := &store.QueryOptions{
|
|
Page: req.Page,
|
|
PageSize: req.PageSize,
|
|
OrderBy: "created_at DESC",
|
|
}
|
|
if opts.Page == 0 {
|
|
opts.Page = 1
|
|
}
|
|
if opts.PageSize == 0 {
|
|
opts.PageSize = constants.DefaultPageSize
|
|
}
|
|
|
|
filters := make(map[string]interface{})
|
|
if req.ShopName != "" {
|
|
filters["shop_name"] = req.ShopName
|
|
}
|
|
|
|
shops, total, err := s.shopStore.List(ctx, opts, filters)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("查询店铺列表失败: %w", err)
|
|
}
|
|
|
|
if len(shops) == 0 {
|
|
return &dto.ShopCommissionSummaryPageResult{
|
|
Items: []dto.ShopCommissionSummaryItem{},
|
|
Total: 0,
|
|
Page: opts.Page,
|
|
Size: opts.PageSize,
|
|
}, nil
|
|
}
|
|
|
|
shopIDs := make([]uint, 0, len(shops))
|
|
for _, shop := range shops {
|
|
shopIDs = append(shopIDs, shop.ID)
|
|
}
|
|
|
|
walletSummaries, err := s.walletStore.GetShopCommissionSummaryBatch(ctx, shopIDs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("查询店铺钱包汇总失败: %w", err)
|
|
}
|
|
|
|
withdrawnAmounts, err := s.commissionWithdrawalReqStore.SumAmountByShopIDsAndStatus(ctx, shopIDs, constants.WithdrawalStatusApproved)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("查询已提现金额失败: %w", err)
|
|
}
|
|
|
|
withdrawingAmounts, err := s.commissionWithdrawalReqStore.SumAmountByShopIDsAndStatus(ctx, shopIDs, constants.WithdrawalStatusPending)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("查询提现中金额失败: %w", err)
|
|
}
|
|
|
|
primaryAccounts, err := s.accountStore.GetPrimaryAccountsByShopIDs(ctx, shopIDs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("查询主账号失败: %w", err)
|
|
}
|
|
|
|
accountMap := make(map[uint]*model.Account)
|
|
for _, acc := range primaryAccounts {
|
|
if acc.ShopID != nil {
|
|
accountMap[*acc.ShopID] = acc
|
|
}
|
|
}
|
|
|
|
items := make([]dto.ShopCommissionSummaryItem, 0, len(shops))
|
|
for _, shop := range shops {
|
|
if req.Username != "" {
|
|
acc := accountMap[shop.ID]
|
|
if acc == nil || !containsSubstring(acc.Username, req.Username) {
|
|
total--
|
|
continue
|
|
}
|
|
}
|
|
|
|
item := s.buildCommissionSummaryItem(shop, walletSummaries[shop.ID], withdrawnAmounts[shop.ID], withdrawingAmounts[shop.ID], accountMap[shop.ID])
|
|
items = append(items, item)
|
|
}
|
|
|
|
return &dto.ShopCommissionSummaryPageResult{
|
|
Items: items,
|
|
Total: total,
|
|
Page: opts.Page,
|
|
Size: opts.PageSize,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) buildCommissionSummaryItem(shop *model.Shop, walletSummary *postgres.ShopCommissionSummary, withdrawnAmount, withdrawingAmount int64, account *model.Account) dto.ShopCommissionSummaryItem {
|
|
var balance, frozenBalance int64
|
|
if walletSummary != nil {
|
|
balance = walletSummary.Balance
|
|
frozenBalance = walletSummary.FrozenBalance
|
|
}
|
|
|
|
totalCommission := balance + frozenBalance + withdrawnAmount
|
|
unwithdrawCommission := totalCommission - withdrawnAmount
|
|
availableCommission := balance - withdrawingAmount
|
|
if availableCommission < 0 {
|
|
availableCommission = 0
|
|
}
|
|
|
|
var username, phone string
|
|
if account != nil {
|
|
username = account.Username
|
|
phone = account.Phone
|
|
}
|
|
|
|
return dto.ShopCommissionSummaryItem{
|
|
ShopID: shop.ID,
|
|
ShopName: shop.ShopName,
|
|
ShopCode: shop.ShopCode,
|
|
Username: username,
|
|
Phone: phone,
|
|
TotalCommission: totalCommission,
|
|
WithdrawnCommission: withdrawnAmount,
|
|
UnwithdrawCommission: unwithdrawCommission,
|
|
FrozenCommission: frozenBalance,
|
|
WithdrawingCommission: withdrawingAmount,
|
|
AvailableCommission: availableCommission,
|
|
CreatedAt: shop.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
}
|
|
}
|
|
|
|
func (s *Service) ListShopWithdrawalRequests(ctx context.Context, shopID uint, req *dto.ShopWithdrawalRequestListReq) (*dto.ShopWithdrawalRequestPageResult, error) {
|
|
_, err := s.shopStore.GetByID(ctx, shopID)
|
|
if err != nil {
|
|
return nil, errors.New(errors.CodeShopNotFound, "店铺不存在")
|
|
}
|
|
|
|
opts := &store.QueryOptions{
|
|
Page: req.Page,
|
|
PageSize: req.PageSize,
|
|
OrderBy: "created_at DESC",
|
|
}
|
|
if opts.Page == 0 {
|
|
opts.Page = 1
|
|
}
|
|
if opts.PageSize == 0 {
|
|
opts.PageSize = constants.DefaultPageSize
|
|
}
|
|
|
|
filters := &postgres.WithdrawalRequestListFilters{
|
|
ShopID: shopID,
|
|
WithdrawalNo: req.WithdrawalNo,
|
|
}
|
|
|
|
if req.StartTime != "" {
|
|
t, err := time.Parse("2006-01-02 15:04:05", req.StartTime)
|
|
if err == nil {
|
|
filters.StartTime = &t
|
|
}
|
|
}
|
|
if req.EndTime != "" {
|
|
t, err := time.Parse("2006-01-02 15:04:05", req.EndTime)
|
|
if err == nil {
|
|
filters.EndTime = &t
|
|
}
|
|
}
|
|
|
|
requests, total, err := s.commissionWithdrawalReqStore.ListByShopID(ctx, opts, filters)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("查询提现记录失败: %w", err)
|
|
}
|
|
|
|
shop, _ := s.shopStore.GetByID(ctx, shopID)
|
|
shopHierarchy := s.buildShopHierarchyPath(ctx, shop)
|
|
|
|
applicantIDs := make([]uint, 0)
|
|
processorIDs := make([]uint, 0)
|
|
for _, r := range requests {
|
|
if r.ApplicantID > 0 {
|
|
applicantIDs = append(applicantIDs, r.ApplicantID)
|
|
}
|
|
if r.ProcessorID > 0 {
|
|
processorIDs = append(processorIDs, r.ProcessorID)
|
|
}
|
|
}
|
|
|
|
applicantMap := make(map[uint]string)
|
|
processorMap := make(map[uint]string)
|
|
|
|
if len(applicantIDs) > 0 {
|
|
accounts, _ := s.accountStore.GetByIDs(ctx, applicantIDs)
|
|
for _, acc := range accounts {
|
|
applicantMap[acc.ID] = acc.Username
|
|
}
|
|
}
|
|
if len(processorIDs) > 0 {
|
|
accounts, _ := s.accountStore.GetByIDs(ctx, processorIDs)
|
|
for _, acc := range accounts {
|
|
processorMap[acc.ID] = acc.Username
|
|
}
|
|
}
|
|
|
|
items := make([]dto.ShopWithdrawalRequestItem, 0, len(requests))
|
|
for _, r := range requests {
|
|
item := s.buildWithdrawalRequestItem(r, shop.ShopName, shopHierarchy, applicantMap, processorMap)
|
|
items = append(items, item)
|
|
}
|
|
|
|
return &dto.ShopWithdrawalRequestPageResult{
|
|
Items: items,
|
|
Total: total,
|
|
Page: opts.Page,
|
|
Size: opts.PageSize,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) buildWithdrawalRequestItem(r *model.CommissionWithdrawalRequest, shopName, shopHierarchy string, applicantMap, processorMap map[uint]string) dto.ShopWithdrawalRequestItem {
|
|
var processorID *uint
|
|
if r.ProcessorID > 0 {
|
|
processorID = &r.ProcessorID
|
|
}
|
|
|
|
var accountName, accountNumber, bankName string
|
|
if len(r.AccountInfo) > 0 {
|
|
var info map[string]interface{}
|
|
if err := json.Unmarshal(r.AccountInfo, &info); err == nil {
|
|
if v, ok := info["account_name"].(string); ok {
|
|
accountName = v
|
|
}
|
|
if v, ok := info["account_number"].(string); ok {
|
|
accountNumber = v
|
|
}
|
|
if v, ok := info["bank_name"].(string); ok {
|
|
bankName = v
|
|
}
|
|
}
|
|
}
|
|
|
|
var processedAt, paidAt string
|
|
if r.ProcessedAt != nil {
|
|
processedAt = r.ProcessedAt.Format("2006-01-02 15:04:05")
|
|
}
|
|
if r.PaidAt != nil {
|
|
paidAt = r.PaidAt.Format("2006-01-02 15:04:05")
|
|
}
|
|
|
|
return dto.ShopWithdrawalRequestItem{
|
|
ID: r.ID,
|
|
WithdrawalNo: r.WithdrawalNo,
|
|
Amount: r.Amount,
|
|
FeeRate: r.FeeRate,
|
|
Fee: r.Fee,
|
|
ActualAmount: r.ActualAmount,
|
|
Status: r.Status,
|
|
StatusName: getWithdrawalStatusName(r.Status),
|
|
ShopID: r.ShopID,
|
|
ShopName: shopName,
|
|
ShopHierarchy: shopHierarchy,
|
|
ApplicantID: r.ApplicantID,
|
|
ApplicantName: applicantMap[r.ApplicantID],
|
|
ProcessorID: processorID,
|
|
ProcessorName: processorMap[r.ProcessorID],
|
|
WithdrawalMethod: r.WithdrawalMethod,
|
|
PaymentType: r.PaymentType,
|
|
AccountName: accountName,
|
|
AccountNumber: accountNumber,
|
|
BankName: bankName,
|
|
RejectReason: r.RejectReason,
|
|
Remark: r.Remark,
|
|
CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
ProcessedAt: processedAt,
|
|
PaidAt: paidAt,
|
|
}
|
|
}
|
|
|
|
func (s *Service) buildShopHierarchyPath(ctx context.Context, shop *model.Shop) string {
|
|
if shop == nil {
|
|
return ""
|
|
}
|
|
|
|
path := shop.ShopName
|
|
current := shop
|
|
depth := 0
|
|
|
|
for current.ParentID != nil && depth < 2 {
|
|
parent, err := s.shopStore.GetByID(ctx, *current.ParentID)
|
|
if err != nil {
|
|
break
|
|
}
|
|
path = parent.ShopName + "_" + path
|
|
current = parent
|
|
depth++
|
|
}
|
|
|
|
return path
|
|
}
|
|
|
|
func (s *Service) ListShopCommissionRecords(ctx context.Context, shopID uint, req *dto.ShopCommissionRecordListReq) (*dto.ShopCommissionRecordPageResult, error) {
|
|
_, err := s.shopStore.GetByID(ctx, shopID)
|
|
if err != nil {
|
|
return nil, errors.New(errors.CodeShopNotFound, "店铺不存在")
|
|
}
|
|
|
|
opts := &store.QueryOptions{
|
|
Page: req.Page,
|
|
PageSize: req.PageSize,
|
|
OrderBy: "created_at DESC",
|
|
}
|
|
if opts.Page == 0 {
|
|
opts.Page = 1
|
|
}
|
|
if opts.PageSize == 0 {
|
|
opts.PageSize = constants.DefaultPageSize
|
|
}
|
|
|
|
filters := &postgres.CommissionRecordListFilters{
|
|
ShopID: shopID,
|
|
CommissionType: req.CommissionType,
|
|
ICCID: req.ICCID,
|
|
DeviceNo: req.DeviceNo,
|
|
OrderNo: req.OrderNo,
|
|
}
|
|
|
|
records, total, err := s.commissionRecordStore.ListByShopID(ctx, opts, filters)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("查询佣金明细失败: %w", err)
|
|
}
|
|
|
|
items := make([]dto.ShopCommissionRecordItem, 0, len(records))
|
|
for _, r := range records {
|
|
item := dto.ShopCommissionRecordItem{
|
|
ID: r.ID,
|
|
Amount: r.Amount,
|
|
BalanceAfter: r.BalanceAfter,
|
|
CommissionType: r.CommissionType,
|
|
Status: r.Status,
|
|
StatusName: getCommissionStatusName(r.Status),
|
|
OrderID: r.OrderID,
|
|
OrderNo: "",
|
|
DeviceNo: "",
|
|
ICCID: "",
|
|
OrderCreatedAt: "",
|
|
CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
|
|
return &dto.ShopCommissionRecordPageResult{
|
|
Items: items,
|
|
Total: total,
|
|
Page: opts.Page,
|
|
Size: opts.PageSize,
|
|
}, nil
|
|
}
|
|
|
|
func getWithdrawalStatusName(status int) string {
|
|
switch status {
|
|
case constants.WithdrawalStatusPending:
|
|
return "待审核"
|
|
case constants.WithdrawalStatusApproved:
|
|
return "已通过"
|
|
case constants.WithdrawalStatusRejected:
|
|
return "已拒绝"
|
|
case constants.WithdrawalStatusPaid:
|
|
return "已到账"
|
|
default:
|
|
return "未知"
|
|
}
|
|
}
|
|
|
|
func getCommissionStatusName(status int) string {
|
|
switch status {
|
|
case constants.CommissionStatusFrozen:
|
|
return "已冻结"
|
|
case constants.CommissionStatusUnfreezing:
|
|
return "解冻中"
|
|
case constants.CommissionStatusReleased:
|
|
return "已发放"
|
|
case constants.CommissionStatusInvalid:
|
|
return "已失效"
|
|
default:
|
|
return "未知"
|
|
}
|
|
}
|
|
|
|
func containsSubstring(s, substr string) bool {
|
|
return len(s) >= len(substr) && (s == substr || len(substr) == 0 || (len(s) > 0 && len(substr) > 0 && contains(s, substr)))
|
|
}
|
|
|
|
func contains(s, substr string) bool {
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
if s[i:i+len(substr)] == substr {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|