All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m35s
145 lines
6.6 KiB
Go
145 lines
6.6 KiB
Go
package refundapproval
|
|
|
|
import (
|
|
"context"
|
|
approvalapp "github.com/break/junhong_cmp_fiber/internal/application/approval"
|
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
|
"github.com/bytedance/sonic"
|
|
"gorm.io/datatypes"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
type RecoveryAuditWriter interface {
|
|
WriteSnapshotRecovery(ctx context.Context, tx *gorm.DB, instance *model.ApprovalInstance, fields []string, actorID uint) error
|
|
WriteRecoveryRequest(ctx context.Context, tx *gorm.DB, instance *model.ApprovalInstance, actorID uint, branch string) error
|
|
}
|
|
|
|
type RecoveryService struct {
|
|
db *gorm.DB
|
|
recovery approvalapp.RecoveryPort
|
|
audit RecoveryAuditWriter
|
|
}
|
|
|
|
func NewRecoveryService(db *gorm.DB, recovery approvalapp.RecoveryPort, audit RecoveryAuditWriter) *RecoveryService {
|
|
return &RecoveryService{db: db, recovery: recovery, audit: audit}
|
|
}
|
|
|
|
func (s *RecoveryService) Execute(ctx context.Context, refundID uint) error {
|
|
actorID := middleware.GetUserIDFromContext(ctx)
|
|
if actorID == 0 {
|
|
return errors.New(errors.CodeUnauthorized, "未认证的恢复操作")
|
|
}
|
|
if s == nil || s.db == nil || s.recovery == nil || refundID == 0 {
|
|
return errors.New(errors.CodeServiceUnavailable, "退款审批恢复能力未配置")
|
|
}
|
|
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
var refund model.RefundRequest
|
|
if err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).First(&refund, refundID).Error; err != nil {
|
|
return errors.New(errors.CodeNotFound, "退款申请不存在")
|
|
}
|
|
if refund.Status != model.RefundStatusPending || refund.LatestAttemptID == 0 || refund.LatestApprovalInstanceID == 0 || refund.ChannelRefundStatus == constants.RefundChannelStatusProcessing || refund.Status == model.RefundStatusChannelFailed {
|
|
return errors.New(errors.CodeConflict, "当前退款状态不允许恢复审批")
|
|
}
|
|
var attempt model.RefundRequestAttempt
|
|
if err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ? AND refund_id = ?", refund.LatestAttemptID, refund.ID).First(&attempt).Error; err != nil {
|
|
return errors.New(errors.CodeConflict, "退款审批尝试关联不一致")
|
|
}
|
|
if attempt.ApprovalInstanceID == nil || *attempt.ApprovalInstanceID != refund.LatestApprovalInstanceID {
|
|
return errors.New(errors.CodeConflict, "退款审批实例关联不一致")
|
|
}
|
|
var instance model.ApprovalInstance
|
|
if err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ? AND business_type = ? AND business_id = ?", refund.LatestApprovalInstanceID, constants.ApprovalBusinessTypeRefund, attempt.ID).First(&instance).Error; err != nil {
|
|
return errors.New(errors.CodeConflict, "退款审批实例业务关联不一致")
|
|
}
|
|
if instance.Status != constants.ApprovalStatusSubmitting && instance.Status != constants.ApprovalStatusSubmissionFailed && instance.Status != constants.ApprovalStatusSubmissionUnknown && instance.Status != constants.ApprovalStatusPending {
|
|
return errors.New(errors.CodeConflict, "审批终态不允许恢复")
|
|
}
|
|
fields, err := fillMissingSnapshot(&instance, &attempt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(fields) > 0 {
|
|
if err := tx.Model(&model.ApprovalInstance{}).Where("id = ?", instance.ID).Update("request_snapshot", datatypes.JSON(instance.RequestSnapshot)).Error; err != nil {
|
|
return errors.Wrap(errors.CodeDatabaseError, err, "补齐退款审批快照失败")
|
|
}
|
|
if s.audit != nil {
|
|
if err := s.audit.WriteSnapshotRecovery(ctx, tx, &instance, fields, actorID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
var context model.WeComApprovalContext
|
|
if err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("approval_instance_id = ? AND business_type = ?", instance.ID, constants.ApprovalBusinessTypeRefund).First(&context).Error; err != nil {
|
|
return errors.New(errors.CodeConflict, "企业微信审批上下文不存在或业务类型不一致")
|
|
}
|
|
branch := "active"
|
|
if context.SPNo != "" {
|
|
branch = "sp_no"
|
|
if s.audit != nil {
|
|
if err := s.audit.WriteRecoveryRequest(ctx, tx, &instance, actorID, branch); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return s.recovery.EnqueueSubmittedSync(ctx, tx, instance.ID)
|
|
}
|
|
if context.SubmissionStatus == constants.WeComSubmissionStatusUnknown || instance.Status == constants.ApprovalStatusSubmissionUnknown {
|
|
branch = "unknown"
|
|
if s.audit != nil {
|
|
if err := s.audit.WriteRecoveryRequest(ctx, tx, &instance, actorID, branch); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return s.recovery.EnqueueUnknownConfirm(ctx, tx, instance.ID)
|
|
}
|
|
if instance.Status == constants.ApprovalStatusSubmitting || instance.Status == constants.ApprovalStatusPending || context.SubmissionStatus == constants.WeComSubmissionStatusReady || context.SubmissionStatus == constants.WeComSubmissionStatusSending {
|
|
if s.audit != nil {
|
|
if err := s.audit.WriteRecoveryRequest(ctx, tx, &instance, actorID, branch); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
if instance.Status != constants.ApprovalStatusSubmissionFailed || context.SubmissionStatus != constants.WeComSubmissionStatusFailed {
|
|
return errors.New(errors.CodeConflict, "当前审批提交状态不允许恢复")
|
|
}
|
|
branch = "replay"
|
|
if s.audit != nil {
|
|
if err := s.audit.WriteRecoveryRequest(ctx, tx, &instance, actorID, branch); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err = s.recovery.RecoverSubmissionEvent(ctx, tx, instance.ID)
|
|
return err
|
|
})
|
|
}
|
|
func fillMissingSnapshot(instance *model.ApprovalInstance, attempt *model.RefundRequestAttempt) ([]string, error) {
|
|
var snapshot map[string]any
|
|
if err := sonic.Unmarshal(instance.RequestSnapshot, &snapshot); err != nil {
|
|
return nil, errors.Wrap(errors.CodeInvalidStatus, err, "退款审批快照无效")
|
|
}
|
|
if snapshot == nil {
|
|
snapshot = map[string]any{}
|
|
}
|
|
values := map[string]any{constants.ApprovalFieldRefundMethodCode: attempt.Method, constants.ApprovalFieldRefundMethod: constants.RefundMethodName(attempt.Method), constants.ApprovalFieldCustomerAccountInfo: attempt.CustomerAccountInfo, constants.ApprovalFieldCustomerVoucherKey: []string(attempt.CustomerVoucherKeys)}
|
|
fields := make([]string, 0, len(values))
|
|
for key, value := range values {
|
|
if _, ok := snapshot[key]; ok {
|
|
continue
|
|
}
|
|
snapshot[key] = value
|
|
fields = append(fields, key)
|
|
}
|
|
if len(fields) > 0 {
|
|
encoded, err := sonic.Marshal(snapshot)
|
|
if err != nil {
|
|
return nil, errors.Wrap(errors.CodeInternalError, err, "编码退款审批快照失败")
|
|
}
|
|
instance.RequestSnapshot = encoded
|
|
}
|
|
return fields, nil
|
|
}
|