七月迭代短暂完结,还有很多后端的关键东西没有弄,这是一版赶时间做的东西
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
This commit is contained in:
@@ -13,10 +13,12 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
agentrechargeapp "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
|
||||
walletapp "github.com/break/junhong_cmp_fiber/internal/application/wallet"
|
||||
"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/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/config"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
@@ -44,6 +46,7 @@ type Service struct {
|
||||
agentRechargeStore *postgres.AgentRechargeStore
|
||||
agentWalletStore *postgres.AgentWalletStore
|
||||
agentWalletPosting *walletapp.PostingService
|
||||
offlineCreation *agentrechargeapp.OfflineCreationService
|
||||
shopStore *postgres.ShopStore
|
||||
wechatConfigService WechatConfigServiceInterface
|
||||
auditService AuditServiceInterface
|
||||
@@ -82,6 +85,11 @@ func (s *Service) SetAgentWalletPostingService(service *walletapp.PostingService
|
||||
s.agentWalletPosting = service
|
||||
}
|
||||
|
||||
// SetOfflineCreationService 注入员工线下代充值审批申请用例。
|
||||
func (s *Service) SetOfflineCreationService(service *agentrechargeapp.OfflineCreationService) {
|
||||
s.offlineCreation = service
|
||||
}
|
||||
|
||||
// Create 创建代理充值订单
|
||||
// POST /api/admin/agent-recharges
|
||||
func (s *Service) Create(ctx context.Context, req *dto.CreateAgentRechargeRequest) (*dto.AgentRechargeResponse, error) {
|
||||
@@ -95,12 +103,12 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateAgentRechargeReques
|
||||
}
|
||||
|
||||
// 线下充值仅平台可用
|
||||
if req.PaymentMethod == "offline" && userType != constants.UserTypePlatform && userType != constants.UserTypeSuperAdmin {
|
||||
if req.PaymentMethod == constants.RechargeMethodOffline && userType != constants.UserTypePlatform && userType != constants.UserTypeSuperAdmin {
|
||||
return nil, errors.New(errors.CodeForbidden, "线下充值仅平台管理员可操作")
|
||||
}
|
||||
|
||||
// 线下充值必须上传支付凭证
|
||||
if req.PaymentMethod == "offline" && len(req.PaymentVoucherKey) == 0 {
|
||||
if req.PaymentMethod == constants.RechargeMethodOffline && len(req.PaymentVoucherKey) == 0 {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "线下充值必须上传支付凭证")
|
||||
}
|
||||
|
||||
@@ -108,6 +116,11 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateAgentRechargeReques
|
||||
return nil, errors.New(errors.CodeInvalidParam, "充值金额超出允许范围")
|
||||
}
|
||||
|
||||
rechargeNo := s.generateRechargeNo()
|
||||
if req.PaymentMethod == constants.RechargeMethodOffline {
|
||||
return s.createOffline(ctx, req, userID, userType, rechargeNo)
|
||||
}
|
||||
|
||||
// 查找目标店铺的主钱包
|
||||
wallet, err := s.agentWalletStore.GetMainWallet(ctx, req.ShopID)
|
||||
if err != nil {
|
||||
@@ -140,8 +153,6 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateAgentRechargeReques
|
||||
paymentChannel = "offline"
|
||||
}
|
||||
|
||||
rechargeNo := s.generateRechargeNo()
|
||||
|
||||
record := &model.AgentRechargeRecord{
|
||||
UserID: userID,
|
||||
AgentWalletID: wallet.ID,
|
||||
@@ -170,12 +181,55 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateAgentRechargeReques
|
||||
zap.Uint("user_id", userID),
|
||||
)
|
||||
|
||||
return toResponse(record, shop.ShopName), nil
|
||||
resp := toResponse(record, shop.ShopName)
|
||||
resp.SubmitterName = s.loadSubmitterNameBestEffort(ctx, record.UserID)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *Service) createOffline(
|
||||
ctx context.Context,
|
||||
req *dto.CreateAgentRechargeRequest,
|
||||
userID uint,
|
||||
userType int,
|
||||
rechargeNo string,
|
||||
) (*dto.AgentRechargeResponse, error) {
|
||||
if s.offlineCreation == nil {
|
||||
return nil, errors.New(errors.CodeServiceUnavailable, "员工线下代充值审批能力未配置")
|
||||
}
|
||||
result, err := s.offlineCreation.Execute(ctx, agentrechargeapp.CreateOfflineCommand{
|
||||
SubmitterAccountID: userID,
|
||||
SubmitterUserType: userType,
|
||||
ShopID: req.ShopID,
|
||||
RechargeNo: rechargeNo,
|
||||
Amount: req.Amount,
|
||||
PaymentVoucherKeys: req.PaymentVoucherKey,
|
||||
Remark: req.Remark,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := toResponse(result.Record, result.ShopName)
|
||||
resp.SubmitterName = result.SubmitterName
|
||||
resp.ApprovalProvider = constants.IntegrationProviderWeCom
|
||||
resp.ApprovalStatus = &result.ApprovalStatus
|
||||
resp.ApprovalStatusName = constants.GetApprovalStatusName(result.ApprovalStatus)
|
||||
s.logger.Info("创建员工线下代充值审批申请成功",
|
||||
zap.Uint("recharge_id", result.Record.ID),
|
||||
zap.String("recharge_no", result.Record.RechargeNo),
|
||||
zap.Uint("approval_instance_id", *result.Record.ApprovalInstanceID),
|
||||
zap.Int64("amount", result.Record.Amount),
|
||||
zap.Uint("shop_id", result.Record.ShopID),
|
||||
zap.Uint("submitter_id", result.Record.UserID),
|
||||
)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// OfflinePay 线下充值确认
|
||||
// POST /api/admin/agent-recharges/:id/offline-pay
|
||||
func (s *Service) OfflinePay(ctx context.Context, id uint, req *dto.AgentOfflinePayRequest) (*dto.AgentRechargeResponse, error) {
|
||||
if !legacyOfflineRechargePayEnabled() {
|
||||
return nil, errors.New(errors.CodeInvalidStatus, "线下充值人工确认入口已停用,请查看企业微信审批状态")
|
||||
}
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
userType := middleware.GetUserTypeFromContext(ctx)
|
||||
|
||||
@@ -197,9 +251,12 @@ func (s *Service) OfflinePay(ctx context.Context, id uint, req *dto.AgentOffline
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询充值记录失败")
|
||||
}
|
||||
|
||||
if record.PaymentMethod != "offline" {
|
||||
if record.PaymentMethod != constants.RechargeMethodOffline {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "该订单非线下充值,不支持此操作")
|
||||
}
|
||||
if record.ApprovalInstanceID != nil {
|
||||
return nil, errors.New(errors.CodeInvalidStatus, "该线下充值申请由企业微信审批决定,不能人工确认")
|
||||
}
|
||||
if record.Status != constants.RechargeStatusPending {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "该订单状态不允许确认支付")
|
||||
}
|
||||
@@ -266,7 +323,19 @@ func (s *Service) OfflinePay(ctx context.Context, id uint, req *dto.AgentOffline
|
||||
record.PaidAt = &now
|
||||
record.CompletedAt = &now
|
||||
|
||||
return toResponse(record, shopName), nil
|
||||
resp := toResponse(record, shopName)
|
||||
resp.SubmitterName = s.loadSubmitterNameBestEffort(ctx, record.UserID)
|
||||
approvalSummaries, err := s.loadApprovalSummaries(ctx, []*model.AgentRechargeRecord{record})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
applyApprovalSummary(resp, approvalSummaries, record.ApprovalInstanceID)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func legacyOfflineRechargePayEnabled() bool {
|
||||
cfg := config.Get()
|
||||
return cfg == nil || cfg.Approval.LegacyOfflineRechargePayEnabled
|
||||
}
|
||||
|
||||
// HandlePaymentCallback 处理支付回调
|
||||
@@ -388,6 +457,9 @@ func (s *Service) Reject(ctx context.Context, id uint, rejectionReason string) e
|
||||
if record.Status != constants.RechargeStatusPending {
|
||||
return errors.New(errors.CodeInvalidStatus, "仅待支付订单可驳回")
|
||||
}
|
||||
if record.ApprovalInstanceID != nil {
|
||||
return errors.New(errors.CodeInvalidStatus, "该线下充值申请由企业微信审批决定,不能人工驳回")
|
||||
}
|
||||
|
||||
if err := s.agentRechargeStore.UpdateStatusWithRejection(ctx, id, rejectionReason); err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
@@ -420,7 +492,14 @@ func (s *Service) GetByID(ctx context.Context, id uint) (*dto.AgentRechargeRespo
|
||||
shopName = shop.ShopName
|
||||
}
|
||||
|
||||
return toResponse(record, shopName), nil
|
||||
resp := toResponse(record, shopName)
|
||||
resp.SubmitterName = s.loadSubmitterNameBestEffort(ctx, record.UserID)
|
||||
approvalSummaries, err := s.loadApprovalSummaries(ctx, []*model.AgentRechargeRecord{record})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
applyApprovalSummary(resp, approvalSummaries, record.ApprovalInstanceID)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// List 分页查询充值订单列表
|
||||
@@ -475,10 +554,27 @@ func (s *Service) List(ctx context.Context, req *dto.AgentRechargeListRequest) (
|
||||
}
|
||||
}
|
||||
}
|
||||
submitterIDs := make([]uint, 0, len(records))
|
||||
for _, record := range records {
|
||||
if record.UserID > 0 {
|
||||
submitterIDs = append(submitterIDs, record.UserID)
|
||||
}
|
||||
}
|
||||
submitterNames, err := s.loadSubmitterNames(ctx, submitterIDs)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(errors.CodeDatabaseError, err, "查询充值提交人失败")
|
||||
}
|
||||
approvalSummaries, err := s.loadApprovalSummaries(ctx, records)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
list := make([]*dto.AgentRechargeResponse, 0, len(records))
|
||||
for _, r := range records {
|
||||
list = append(list, toResponse(r, shopMap[r.ShopID]))
|
||||
item := toResponse(r, shopMap[r.ShopID])
|
||||
item.SubmitterName = submitterNames[r.UserID]
|
||||
applyApprovalSummary(item, approvalSummaries, r.ApprovalInstanceID)
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
return list, total, nil
|
||||
@@ -495,20 +591,22 @@ func (s *Service) generateRechargeNo() string {
|
||||
// toResponse 将模型转换为响应 DTO
|
||||
func toResponse(record *model.AgentRechargeRecord, shopName string) *dto.AgentRechargeResponse {
|
||||
resp := &dto.AgentRechargeResponse{
|
||||
ID: record.ID,
|
||||
RechargeNo: record.RechargeNo,
|
||||
ShopID: record.ShopID,
|
||||
ShopName: shopName,
|
||||
AgentWalletID: record.AgentWalletID,
|
||||
Amount: record.Amount,
|
||||
PaymentMethod: record.PaymentMethod,
|
||||
PaymentVoucherKey: []string(record.PaymentVoucherKey),
|
||||
Remark: record.Remark,
|
||||
Status: record.Status,
|
||||
StatusName: constants.GetRechargeStatusName(record.Status),
|
||||
RejectionReason: record.RejectionReason,
|
||||
CreatedAt: record.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
UpdatedAt: record.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||
ID: record.ID,
|
||||
RechargeNo: record.RechargeNo,
|
||||
ShopID: record.ShopID,
|
||||
ShopName: shopName,
|
||||
AgentWalletID: record.AgentWalletID,
|
||||
Amount: record.Amount,
|
||||
PaymentMethod: record.PaymentMethod,
|
||||
PaymentVoucherKey: []string(record.PaymentVoucherKey),
|
||||
Remark: record.Remark,
|
||||
Status: record.Status,
|
||||
StatusName: constants.GetRechargeStatusName(record.Status),
|
||||
RejectionReason: record.RejectionReason,
|
||||
SubmitterID: record.UserID,
|
||||
ApprovalInstanceID: record.ApprovalInstanceID,
|
||||
CreatedAt: record.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
UpdatedAt: record.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
if record.PaymentChannel != nil {
|
||||
@@ -531,3 +629,74 @@ func toResponse(record *model.AgentRechargeRecord, shopName string) *dto.AgentRe
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
type approvalSummary struct {
|
||||
Provider string
|
||||
Status int
|
||||
}
|
||||
|
||||
func (s *Service) loadApprovalSummaries(
|
||||
ctx context.Context,
|
||||
records []*model.AgentRechargeRecord,
|
||||
) (map[uint]approvalSummary, error) {
|
||||
ids := make([]uint, 0, len(records))
|
||||
seen := make(map[uint]struct{}, len(records))
|
||||
for _, record := range records {
|
||||
if record == nil || record.ApprovalInstanceID == nil || *record.ApprovalInstanceID == 0 {
|
||||
continue
|
||||
}
|
||||
id := *record.ApprovalInstanceID
|
||||
if _, exists := seen[id]; exists {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
summaries := make(map[uint]approvalSummary, len(ids))
|
||||
if len(ids) == 0 {
|
||||
return summaries, nil
|
||||
}
|
||||
var instances []model.ApprovalInstance
|
||||
if err := s.db.WithContext(ctx).Select("id", "provider", "status").Where("id IN ?", ids).Find(&instances).Error; err != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询充值审批状态失败")
|
||||
}
|
||||
for _, instance := range instances {
|
||||
summaries[instance.ID] = approvalSummary{Provider: instance.Provider, Status: instance.Status}
|
||||
}
|
||||
return summaries, nil
|
||||
}
|
||||
|
||||
func applyApprovalSummary(response *dto.AgentRechargeResponse, summaries map[uint]approvalSummary, instanceID *uint) {
|
||||
if response == nil || instanceID == nil {
|
||||
return
|
||||
}
|
||||
summary, exists := summaries[*instanceID]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
status := summary.Status
|
||||
response.ApprovalProvider = summary.Provider
|
||||
response.ApprovalStatus = &status
|
||||
response.ApprovalStatusName = constants.GetApprovalStatusName(status)
|
||||
}
|
||||
|
||||
func (s *Service) loadSubmitterNames(ctx context.Context, ids []uint) (map[uint]string, error) {
|
||||
accounts, err := postgres.NewAccountStore(s.db, nil).GetDisplayAccountsByIDs(ctx, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names := make(map[uint]string, len(accounts))
|
||||
for _, account := range accounts {
|
||||
names[account.ID] = account.Username
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func (s *Service) loadSubmitterNameBestEffort(ctx context.Context, id uint) string {
|
||||
names, err := s.loadSubmitterNames(ctx, []uint{id})
|
||||
if err != nil {
|
||||
s.logger.Warn("查询充值提交人失败", zap.Uint("submitter_id", id), zap.Error(err))
|
||||
return ""
|
||||
}
|
||||
return names[id]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user