577 lines
19 KiB
Go
577 lines
19 KiB
Go
package renewchange
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gogf/gf/v2/database/gdb"
|
||
"github.com/gogf/gf/v2/encoding/gjson"
|
||
"github.com/gogf/gf/v2/errors/gerror"
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
"github.com/gogf/gf/v2/os/gtime"
|
||
|
||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
|
||
rc "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/renewchange"
|
||
)
|
||
|
||
var errRenewAlreadyHandled = errors.New("renew batch was already handled")
|
||
|
||
// RenewExecutor executes a confirmed type=101 batch. All business writes and
|
||
// the success state are committed by one database transaction.
|
||
type RenewExecutor struct {
|
||
repo *Repository
|
||
nowFunc NowFunc
|
||
}
|
||
|
||
func NewRenewExecutor(repo *Repository) *RenewExecutor {
|
||
if repo == nil {
|
||
repo = NewRepository()
|
||
}
|
||
return &RenewExecutor{repo: repo, nowFunc: timeNow}
|
||
}
|
||
|
||
func (r *RenewExecutor) SetNowFunc(now NowFunc) { r.nowFunc = now }
|
||
|
||
func (r *RenewExecutor) Execute(ctx context.Context, batchNo string) error {
|
||
batchNo = strings.TrimSpace(batchNo)
|
||
if batchNo == "" {
|
||
return gerror.New("renew batch number is empty")
|
||
}
|
||
|
||
main, err := r.repo.LoadProcessingBatch(ctx, batchNo, consts.BatchTypeRenew)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if strings.TrimSpace(main.OrderSn) != "" {
|
||
g.Log().Warningf(ctx, "renew batch already has order, skip duplicate: batchNo=%s orderSn=%s", batchNo, main.OrderSn)
|
||
return nil
|
||
}
|
||
details, err := r.repo.LoadRunningDetails(ctx, batchNo, consts.BatchTypeRenew)
|
||
if err != nil {
|
||
return gerror.Wrap(err, "load renew details failed")
|
||
}
|
||
if len(details) == 0 {
|
||
return r.finalizeFailure(ctx, main, "renew batch has no runnable details")
|
||
}
|
||
now := pnow(r.nowFunc)
|
||
orderSN, err := r.apply(ctx, main, details, now)
|
||
if errors.Is(err, errRenewAlreadyHandled) {
|
||
return nil
|
||
}
|
||
if err != nil {
|
||
return r.finalizeFailure(ctx, main, err.Error())
|
||
}
|
||
|
||
snap, _ := r.repo.ResolveResultSnapshot(ctx, main.BatchNo, main.BatchType)
|
||
_ = r.repo.WriteResultFileSafe(ctx, main.BatchNo, main.BatchType, main.TenantId, main.CreatedDept, main.CreatedBy, snap)
|
||
g.Log().Infof(ctx, "renew batch success, batchNo=%s, orderSn=%s, tenant=%s", main.BatchNo, orderSN, main.TenantId)
|
||
return nil
|
||
}
|
||
|
||
type renewExecution struct {
|
||
detail *rc.BatchDetailRow
|
||
card *rc.SimCardInfo
|
||
bind *rc.SimCardBindPkg
|
||
begin time.Time
|
||
end time.Time
|
||
}
|
||
|
||
func (r *RenewExecutor) apply(ctx context.Context, main *rc.BatchMainRow, details []*rc.BatchDetailRow, now time.Time) (string, error) {
|
||
iccids, err := strictIccidsFromDetails(details)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
var orderSN string
|
||
err = r.repo.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||
lockedMain, err := r.repo.Tables().LockRenewBatchMain(ctx, tx, main.BatchNo, consts.BatchTypeRenew)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if strings.TrimSpace(lockedMain.OrderSn) != "" || lockedMain.BatchAsyncStatus != consts.BatchAsyncStatusProcessing {
|
||
return errRenewAlreadyHandled
|
||
}
|
||
lockedDetails, err := r.repo.Tables().LockRenewDetails(ctx, tx, main.BatchNo, consts.BatchTypeRenew)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err = validateRunningRenewDetails(lockedDetails, details); err != nil {
|
||
return err
|
||
}
|
||
details = lockedDetails
|
||
|
||
cards, err := r.repo.Tables().LockCardsByIccids(ctx, tx, iccids)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
bindRows, err := r.repo.Tables().LockRenewBindRowsByIccids(ctx, tx, iccids)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
binds, futureBinds, err := resolveCurrentSubscriptions(bindRows, iccids)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
contract, err := r.ensureRenewExecutionContract(ctx, tx, lockedMain, details, cards, binds, futureBinds, now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
executions, err := validateRenewExecutions(contract, details, cards, binds, futureBinds, now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
pkg, err := r.repo.Tables().LoadRenewPackage(ctx, tx, contract.TenantID, contract.PackageCode)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
orderSN, err = rc.NextOrderSN(now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
order, err := BuildOrderDO(contract, orderSN, now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
orderID, err := r.repo.Tables().InsertContractOrder(ctx, tx, order)
|
||
if err != nil {
|
||
return gerror.Wrap(err, "insert renew order failed")
|
||
}
|
||
minBegin, maxEnd := renewServiceRange(executions)
|
||
item, err := BuildOrderItemDO(contract, OrderProductContract{
|
||
ID: pkg.Id,
|
||
Code: pkg.PackageCode,
|
||
Name: pkg.PackageName,
|
||
Type: pkg.PackageType,
|
||
}, orderID, orderSN, minBegin, maxEnd, now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err = r.repo.Tables().InsertContractOrderItem(ctx, tx, item); err != nil {
|
||
return gerror.Wrap(err, "insert renew order item failed")
|
||
}
|
||
|
||
for _, execution := range executions {
|
||
subscriptionNo, err := generateRenewSubscriptionNo(now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
newBind := buildRenewBind(contract, pkg, execution, orderSN, subscriptionNo, now)
|
||
if err = r.repo.Tables().InsertBind(ctx, tx, newBind); err != nil {
|
||
return err
|
||
}
|
||
packageStatus := renewPackageStatus(now, execution.card.FlowPkgBeginAt, newBind.ExpectedExpireTime)
|
||
cardUpdate, err := BuildRenewCardUpdate(contract, execution.end, packageStatus, now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err = r.repo.Tables().UpdateRenewCard(ctx, tx, execution.card.Iccid, cardUpdate); err != nil {
|
||
return err
|
||
}
|
||
snapshot := buildRenewDetailSnapshot(execution, contract, pkg, orderSN, subscriptionNo)
|
||
if err = r.repo.MarkDetailSuccess(ctx, tx, main.BatchNo, consts.BatchTypeRenew, execution.detail.RowNo, snapshot, contract.OperatorUserID); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
info := mergeBatchInfo(lockedMain.BatchInfo, map[string]any{
|
||
"orderSn": orderSN,
|
||
"packageCode": contract.PackageCode,
|
||
"packageName": pkg.PackageName,
|
||
"packageType": pkg.PackageType,
|
||
"billingMethod": contract.BillingMethod,
|
||
"renewDurationUnit": contract.RenewDurationUnit,
|
||
"renewDurationUnitText": contract.RenewDurationUnitText,
|
||
"durationValue": contract.DisplayDurationValue(),
|
||
"actualDurationMonths": contract.DurationMonths,
|
||
"durationMonths": contract.DurationMonths,
|
||
"giftDurationMonths": contract.GiftDurationMonths,
|
||
"amount": contract.TotalAmount(),
|
||
"quantity": contract.Quantity,
|
||
"renewTime": now.Format("2006-01-02 15:04:05"),
|
||
})
|
||
if err = r.repo.UpdateBatchMainInfo(ctx, tx, main.BatchNo, consts.BatchTypeRenew, &rc.BatchMainRow{
|
||
BatchType: consts.BatchTypeRenew,
|
||
BatchAsyncStatus: consts.BatchAsyncStatusSuccess,
|
||
RunResult: consts.BatchRunResultSuccess,
|
||
TotalCount: lockedMain.TotalCount,
|
||
ValidCount: len(executions),
|
||
SuccessCount: len(executions),
|
||
FailCount: 0,
|
||
BatchInfo: gjson.New(info),
|
||
OrderSn: orderSN,
|
||
StartTime: lockedMain.StartTime,
|
||
EndTime: gtime.NewFromTime(now),
|
||
TargetDeptId: contract.CustomerAgentID,
|
||
UpdatedBy: contract.OperatorUserID,
|
||
}); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
})
|
||
return orderSN, err
|
||
}
|
||
|
||
func validateRenewExecutions(contract *ExecutionContract, details []*rc.BatchDetailRow, cards map[string]*rc.SimCardInfo, binds map[string]*rc.SimCardBindPkg, futureBinds map[string][]*rc.SimCardBindPkg, now time.Time) ([]renewExecution, error) {
|
||
if contract == nil {
|
||
return nil, gerror.New("renew execution contract is missing")
|
||
}
|
||
if contract.Quantity != len(details) {
|
||
return nil, gerror.New("renew detail quantity does not match confirmed contract")
|
||
}
|
||
seen := make(map[string]struct{}, len(details))
|
||
result := make([]renewExecution, 0, len(details))
|
||
for _, detail := range details {
|
||
if detail == nil {
|
||
return nil, gerror.New("renew detail is nil")
|
||
}
|
||
iccid := strings.TrimSpace(detail.ItemId)
|
||
if iccid == "" {
|
||
return nil, gerror.New("renew detail ICCID is empty")
|
||
}
|
||
if _, exists := seen[iccid]; exists {
|
||
return nil, gerror.Newf("renew detail ICCID is duplicated: %s", iccid)
|
||
}
|
||
seen[iccid] = struct{}{}
|
||
card := cards[iccid]
|
||
if card == nil {
|
||
return nil, gerror.Newf("renew card not found: %s", iccid)
|
||
}
|
||
bind := binds[iccid]
|
||
if bind == nil {
|
||
return nil, gerror.Newf("renew latest subscription not found: %s", iccid)
|
||
}
|
||
var expectedMSISDN, expectedIMSI string
|
||
if detail.Snapshot != nil {
|
||
expectedMSISDN = strings.TrimSpace(detail.Snapshot.Get("msisdn").String())
|
||
expectedIMSI = strings.TrimSpace(detail.Snapshot.Get("imsi").String())
|
||
}
|
||
if err := validateCurrentSubscription(card, bind, subscriptionValidation{
|
||
ICCID: iccid,
|
||
MSISDN: expectedMSISDN,
|
||
IMSI: expectedIMSI,
|
||
TenantID: contract.TenantID,
|
||
CustomerDeptID: contract.CustomerAgentID,
|
||
PackageCode: contract.PackageCode,
|
||
SourceBatchNo: contract.SourceBatchNo,
|
||
}); err != nil {
|
||
return nil, gerror.Wrapf(err, "validate renew current subscription failed, iccid:%s", iccid)
|
||
}
|
||
// The latest subscription is the source of truth for the next renewal
|
||
// start. The legacy Admin transaction did not require the denormalized
|
||
// card expiration to equal that subscription; future subscriptions can
|
||
// legitimately make the two values differ.
|
||
renewBase := renewBaseSubscriptionForCard(card, bind, futureBinds[iccid])
|
||
if err := validateRenewBaseWindow(renewBase, now, iccid); err != nil {
|
||
return nil, err
|
||
}
|
||
begin, ok := renewBeginTimeFromExpire(renewBase.ExpectedExpireTime)
|
||
if !ok {
|
||
return nil, gerror.Newf("ICCID没有预计到期时间:%s", strings.TrimSpace(iccid))
|
||
}
|
||
if contract.PackageBeginTime != nil {
|
||
begin = contract.PackageBeginTime.Time
|
||
}
|
||
result = append(result, renewExecution{
|
||
detail: detail,
|
||
card: card,
|
||
bind: renewBase,
|
||
begin: begin,
|
||
end: contract.ServiceEnd(begin),
|
||
})
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func renewBaseSubscription(current *rc.SimCardBindPkg, future []*rc.SimCardBindPkg) *rc.SimCardBindPkg {
|
||
base := current
|
||
for _, bind := range future {
|
||
if bind == nil {
|
||
continue
|
||
}
|
||
if base == nil || subscriptionAfter(bind, base) {
|
||
base = bind
|
||
}
|
||
}
|
||
return base
|
||
}
|
||
|
||
func renewBaseSubscriptionForCard(card *rc.SimCardInfo, current *rc.SimCardBindPkg, future []*rc.SimCardBindPkg) *rc.SimCardBindPkg {
|
||
base := renewBaseSubscription(current, future)
|
||
cardExpireAt := latestCardRenewExpireTime(card)
|
||
if cardExpireAt == nil {
|
||
return base
|
||
}
|
||
if base == nil {
|
||
return &rc.SimCardBindPkg{
|
||
Iccid: strings.TrimSpace(card.Iccid),
|
||
PackageCode: strings.TrimSpace(card.CurrentPackageCode),
|
||
ExpectedExpireTime: cardExpireAt,
|
||
}
|
||
}
|
||
if base.ExpectedExpireTime != nil && !cardExpireAt.After(base.ExpectedExpireTime) {
|
||
return base
|
||
}
|
||
clone := *base
|
||
clone.ExpectedExpireTime = cardExpireAt
|
||
return &clone
|
||
}
|
||
|
||
func latestCardRenewExpireTime(card *rc.SimCardInfo) *gtime.Time {
|
||
if card == nil {
|
||
return nil
|
||
}
|
||
var expireAt *gtime.Time
|
||
for _, candidate := range []*gtime.Time{
|
||
card.FlowPkgEndAt,
|
||
card.FlowPkgPresentAt,
|
||
card.FlowPkgRealPresent,
|
||
} {
|
||
if candidate == nil || candidate.IsZero() {
|
||
continue
|
||
}
|
||
if expireAt == nil || candidate.After(expireAt) {
|
||
expireAt = candidate
|
||
}
|
||
}
|
||
return expireAt
|
||
}
|
||
|
||
func validateRenewBaseWindow(base *rc.SimCardBindPkg, now time.Time, iccid string) error {
|
||
if base == nil || base.ExpectedExpireTime == nil {
|
||
return gerror.Newf("ICCID没有预计到期时间:%s", strings.TrimSpace(iccid))
|
||
}
|
||
if renewExpireAfterAllowedWindow(base.ExpectedExpireTime, now) {
|
||
return gerror.Newf("%s:%s", renewExpireFailureReason, strings.TrimSpace(iccid))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func validateRunningRenewDetails(locked, loaded []*rc.BatchDetailRow) error {
|
||
if len(locked) != len(loaded) {
|
||
return gerror.New("renew detail count changed during execution")
|
||
}
|
||
for i, detail := range locked {
|
||
if detail == nil || detail.DetailStatus != consts.BatchDetailStatusRunning {
|
||
return gerror.New("renew detail is no longer running")
|
||
}
|
||
loadedDetail := loaded[i]
|
||
if loadedDetail == nil || detail.Id != loadedDetail.Id || detail.RowNo != loadedDetail.RowNo ||
|
||
strings.TrimSpace(detail.ItemId) != strings.TrimSpace(loadedDetail.ItemId) {
|
||
return gerror.New("renew detail changed during execution")
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func renewServiceRange(executions []renewExecution) (time.Time, time.Time) {
|
||
minBegin := executions[0].begin
|
||
maxEnd := executions[0].end
|
||
for _, execution := range executions[1:] {
|
||
if execution.begin.Before(minBegin) {
|
||
minBegin = execution.begin
|
||
}
|
||
if execution.end.After(maxEnd) {
|
||
maxEnd = execution.end
|
||
}
|
||
}
|
||
return minBegin, maxEnd
|
||
}
|
||
|
||
func generateRenewSubscriptionNo(now time.Time) (string, error) {
|
||
orderSN, err := rc.NextOrderSN(now)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return "SUB" + strings.TrimPrefix(orderSN, "SO"), nil
|
||
}
|
||
|
||
func buildRenewBind(contract *ExecutionContract, pkg *rc.FlowPackageInfo, execution renewExecution, orderSN, subscriptionNo string, now time.Time) *rc.SimCardBindPkg {
|
||
remark := contract.Remark
|
||
if remark == "" {
|
||
remark = "card renew subscription"
|
||
}
|
||
return &rc.SimCardBindPkg{
|
||
SubscriptionNo: subscriptionNo,
|
||
BatchNo: contract.BatchNo,
|
||
Iccid: execution.card.Iccid,
|
||
Msisdn: execution.card.Msisdn,
|
||
DeptId: contract.CustomerAgentID,
|
||
PackageType: pkg.PackageType,
|
||
PackageCode: pkg.PackageCode,
|
||
PreviousPackageCode: pkg.PackageCode,
|
||
BusinessType: consts.PackageBusinessTypeRenew,
|
||
EffectiveStatus: consts.EffectiveStatusPending,
|
||
HandleTime: gtime.NewFromTime(now),
|
||
EffectiveTime: gtime.NewFromTime(execution.begin),
|
||
ExpectedExpireTime: gtime.NewFromTime(execution.end),
|
||
DurationMonths: contract.DurationMonths,
|
||
GiftDurationMonths: contract.GiftDurationMonths,
|
||
OrderCode: orderSN,
|
||
Remark: remark,
|
||
TenantId: contract.TenantID,
|
||
CreatedDept: contract.CreatedDeptID,
|
||
CreatedBy: contract.OperatorUserID,
|
||
CreatedAt: gtime.NewFromTime(now),
|
||
UpdatedBy: contract.OperatorUserID,
|
||
UpdatedAt: gtime.NewFromTime(now),
|
||
}
|
||
}
|
||
|
||
func buildRenewDetailSnapshot(execution renewExecution, contract *ExecutionContract, pkg *rc.FlowPackageInfo, orderSN, subscriptionNo string) *gjson.Json {
|
||
data := map[string]any{}
|
||
if execution.detail.Snapshot != nil {
|
||
for key, value := range execution.detail.Snapshot.Map() {
|
||
data[key] = value
|
||
}
|
||
}
|
||
data["iccid"] = execution.card.Iccid
|
||
data["msisdn"] = execution.card.Msisdn
|
||
data["imsi"] = execution.card.Imsi
|
||
data["renewBatchNo"] = contract.BatchNo
|
||
data["packageCode"] = pkg.PackageCode
|
||
data["packageName"] = pkg.PackageName
|
||
data["oldSubscriptionNo"] = execution.bind.SubscriptionNo
|
||
data["newSubscriptionNo"] = subscriptionNo
|
||
data["orderSn"] = orderSN
|
||
data["oldExpectedExpireTime"] = formatRenewTime(execution.bind.ExpectedExpireTime)
|
||
data["renewEffectiveTime"] = formatRenewTime(gtime.NewFromTime(execution.begin))
|
||
data["renewExpectedExpireTime"] = formatRenewTime(gtime.NewFromTime(execution.end))
|
||
data["cardFlowPkgEndAt"] = formatRenewTime(gtime.NewFromTime(execution.end))
|
||
data["durationValue"] = contract.DisplayDurationValue()
|
||
data["actualDurationMonths"] = contract.DurationMonths
|
||
data["durationMonths"] = contract.DurationMonths
|
||
data["giftDurationMonths"] = contract.GiftDurationMonths
|
||
data["billingMethod"] = contract.BillingMethod
|
||
data["renewDurationUnit"] = contract.RenewDurationUnit
|
||
data["renewDurationUnitText"] = contract.RenewDurationUnitText
|
||
data["renewDate"] = contract.RenewDate
|
||
data["amount"] = contract.TotalAmount()
|
||
data["renewUnitPrice"] = contract.UnitPrice
|
||
data["priceSource"] = contract.PriceSource
|
||
data["rowNo"] = execution.detail.RowNo
|
||
if contract.SourceBatchNo != "" {
|
||
data["sourceBatchNo"] = contract.SourceBatchNo
|
||
}
|
||
return gjson.New(data)
|
||
}
|
||
|
||
func formatRenewTime(value *gtime.Time) string {
|
||
if value == nil {
|
||
return ""
|
||
}
|
||
return value.Format("Y-m-d H:i:s")
|
||
}
|
||
|
||
func renewPackageStatus(now time.Time, begin, end *gtime.Time) int {
|
||
if begin != nil && begin.After(gtime.NewFromTime(now)) {
|
||
return consts.PackageStatusPending
|
||
}
|
||
if end != nil && !end.After(gtime.NewFromTime(now)) {
|
||
return consts.PackageStatusExpired
|
||
}
|
||
return consts.PackageStatusEffective
|
||
}
|
||
|
||
func strictIccidsFromDetails(details []*rc.BatchDetailRow) ([]string, error) {
|
||
seen := make(map[string]struct{}, len(details))
|
||
result := make([]string, 0, len(details))
|
||
for _, detail := range details {
|
||
if detail == nil {
|
||
return nil, gerror.New("renew detail is nil")
|
||
}
|
||
iccid := strings.TrimSpace(detail.ItemId)
|
||
if iccid == "" {
|
||
return nil, gerror.New("renew detail ICCID is empty")
|
||
}
|
||
if _, exists := seen[iccid]; exists {
|
||
return nil, gerror.Newf("renew detail ICCID is duplicated: %s", iccid)
|
||
}
|
||
seen[iccid] = struct{}{}
|
||
result = append(result, iccid)
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// uniqueIccidsFromDetails remains available to the change executor while the
|
||
// renew path uses strictIccidsFromDetails to reject malformed input.
|
||
func uniqueIccidsFromDetails(details []*rc.BatchDetailRow) []string {
|
||
seen := make(map[string]struct{}, len(details))
|
||
result := make([]string, 0, len(details))
|
||
for _, detail := range details {
|
||
if detail == nil {
|
||
continue
|
||
}
|
||
iccid := strings.TrimSpace(detail.ItemId)
|
||
if iccid == "" {
|
||
continue
|
||
}
|
||
if _, exists := seen[iccid]; exists {
|
||
continue
|
||
}
|
||
seen[iccid] = struct{}{}
|
||
result = append(result, iccid)
|
||
}
|
||
return result
|
||
}
|
||
|
||
func (r *RenewExecutor) finalizeFailure(ctx context.Context, main *rc.BatchMainRow, reason string) error {
|
||
now := pnow(r.nowFunc)
|
||
contract, _ := ParseExecutionContract(main)
|
||
if contract == nil {
|
||
contract = renewRefundContractFromBatch(main)
|
||
}
|
||
nowTime := gtime.NewFromTime(now)
|
||
err := r.repo.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||
lockedMain, err := r.repo.Tables().LockRenewBatchMain(ctx, tx, main.BatchNo, main.BatchType)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if contract != nil {
|
||
if err := refundRenewBalanceIfNeeded(ctx, tx, lockedMain, contract, reason, nowTime); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
if err := r.repo.MarkAllDetailsFailed(ctx, tx, main.BatchNo, main.BatchType, reason, main.CreatedBy); err != nil {
|
||
return err
|
||
}
|
||
return r.repo.FinalizeBatchMain(ctx, tx, main.BatchNo, main.BatchType, &rc.BatchMainRow{
|
||
BatchAsyncStatus: consts.BatchAsyncStatusFailed,
|
||
RunResult: consts.BatchRunResultFailed,
|
||
TotalCount: main.TotalCount,
|
||
FailCount: main.TotalCount,
|
||
FailReason: reason,
|
||
EndTime: nowTime,
|
||
UpdatedBy: main.CreatedBy,
|
||
})
|
||
})
|
||
if err != nil {
|
||
return gerror.Wrap(err, "finalize renew failure failed")
|
||
}
|
||
snap, _ := r.repo.ResolveResultSnapshot(ctx, main.BatchNo, main.BatchType)
|
||
_ = r.repo.WriteResultFileSafe(ctx, main.BatchNo, main.BatchType, main.TenantId, main.CreatedDept, main.CreatedBy, snap)
|
||
g.Log().Warningf(ctx, "renew batch failed, batchNo=%s, reason=%s, tenant=%s", main.BatchNo, reason, main.TenantId)
|
||
return nil
|
||
}
|
||
|
||
func mergeBatchInfo(prev *gjson.Json, addition map[string]any) map[string]any {
|
||
out := map[string]any{}
|
||
if prev != nil {
|
||
for key, value := range prev.Map() {
|
||
out[key] = value
|
||
}
|
||
}
|
||
for key, value := range addition {
|
||
out[key] = value
|
||
}
|
||
return out
|
||
}
|
||
|
||
func pnow(fn NowFunc) time.Time {
|
||
if fn == nil {
|
||
return timeNow()
|
||
}
|
||
return fn()
|
||
}
|