Files
junhong_cmp_fiber/internal/service/shop_commission/service.go
huang b9c3875c08
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m3s
feat: 新增数据库迁移,重命名 device_no 为 virtual_no,新增 iot_card.virtual_no 和 package.virtual_ratio 字段
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-14 18:27:28 +08:00

428 lines
12 KiB
Go

package shop_commission
import (
"context"
"encoding/json"
"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
agentWalletStore *postgres.AgentWalletStore
commissionWithdrawalReqStore *postgres.CommissionWithdrawalRequestStore
commissionRecordStore *postgres.CommissionRecordStore
}
func New(
shopStore *postgres.ShopStore,
accountStore *postgres.AccountStore,
agentWalletStore *postgres.AgentWalletStore,
commissionWithdrawalReqStore *postgres.CommissionWithdrawalRequestStore,
commissionRecordStore *postgres.CommissionRecordStore,
) *Service {
return &Service{
shopStore: shopStore,
accountStore: accountStore,
agentWalletStore: agentWalletStore,
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, errors.Wrap(errors.CodeInternalError, 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.agentWalletStore.GetShopCommissionSummaryBatch(ctx, shopIDs)
if err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询店铺钱包汇总失败")
}
withdrawnAmounts, err := s.commissionWithdrawalReqStore.SumAmountByShopIDsAndStatus(ctx, shopIDs, constants.WithdrawalStatusApproved)
if err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询已提现金额失败")
}
withdrawingAmounts, err := s.commissionWithdrawalReqStore.SumAmountByShopIDsAndStatus(ctx, shopIDs, constants.WithdrawalStatusPending)
if err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询提现中金额失败")
}
primaryAccounts, err := s.accountStore.GetPrimaryAccountsByShopIDs(ctx, shopIDs)
if err != nil {
return nil, errors.Wrap(errors.CodeInternalError, 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 *model.AgentWallet, 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, errors.Wrap(errors.CodeInternalError, 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,
CommissionSource: req.CommissionSource,
ICCID: req.ICCID,
DeviceNo: req.VirtualNo,
OrderNo: req.OrderNo,
}
records, total, err := s.commissionRecordStore.ListByShopID(ctx, opts, filters)
if err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "查询佣金明细失败")
}
items := make([]dto.ShopCommissionRecordItem, 0, len(records))
for _, r := range records {
item := dto.ShopCommissionRecordItem{
ID: r.ID,
Amount: r.Amount,
BalanceAfter: r.BalanceAfter,
CommissionSource: r.CommissionSource,
Status: r.Status,
StatusName: getCommissionStatusName(r.Status),
OrderID: r.OrderID,
OrderNo: "",
VirtualNo: "",
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
}