Files
iotlcm/admin-svr/server/internal/logic/iot/cardpackage/renew.go
T

420 lines
14 KiB
Go

package cardpackage
import (
"context"
"fmt"
"strings"
"time"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/iot/packageduration"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gtime"
)
func (s *Service) RenewCurrentPackage(ctx context.Context, tx gdb.TX, in *RenewCurrentPackageInput) (*RenewCurrentPackageOutput, error) {
if err := validateRenewInput(in); err != nil {
return nil, err
}
iccids := uniqueStrings(in.Iccids)
cardsByIccid, err := s.repo.LockCardsByIccids(ctx, tx, iccids)
if err != nil {
return nil, gerror.Wrap(err, "锁定卡信息失败")
}
if len(cardsByIccid) != len(iccids) {
return nil, gerror.New("部分卡信息已变化,请刷新后重试")
}
latestBindsByIccid, err := s.repo.LockLatestBindsByIccids(ctx, tx, iccids)
if err != nil {
return nil, gerror.Wrap(err, "锁定最新订购失败")
}
if len(latestBindsByIccid) != len(iccids) {
return nil, gerror.New("部分卡没有订购记录,不能续费")
}
now := gtime.Now()
sourceBatchNo := strings.TrimSpace(in.SourceBatchNo)
currentPackageCode := ""
customerAgentId := int64(0)
var renewBeginAt *gtime.Time
for _, iccid := range iccids {
card := cardsByIccid[iccid]
bind := latestBindsByIccid[iccid]
if err := validateRenewCardStatus(iccid, card); err != nil {
return nil, err
}
if bind == nil {
return nil, gerror.Newf("ICCID没有订购记录:%s", iccid)
}
if strings.TrimSpace(card.CurrentPackageCode) == "" || card.PackageStatus == consts.PackageStatusNone {
return nil, gerror.Newf("ICCID没有可续费套餐:%s", iccid)
}
if strings.TrimSpace(card.CurrentPackageCode) != strings.TrimSpace(bind.PackageCode) {
return nil, gerror.Newf("ICCID当前套餐与最新订购记录不一致:%s", iccid)
}
baseExpireAt := renewBaseExpireTime(card, bind)
if baseExpireAt == nil {
return nil, gerror.Newf("ICCID最新订购没有预计到期时间:%s", iccid)
}
periodBegin := renewPeriodBeginAt(baseExpireAt, in.PackageBeginTime)
if renewExpireAfterAllowedWindow(baseExpireAt, now) {
return nil, gerror.Newf("距离到期超过6个月,不能续费:%s", iccid)
}
if renewBeginAt == nil {
renewBeginAt = periodBegin
} else if !sameRenewTime(renewBeginAt, periodBegin) {
return nil, gerror.New("当前批次下卡不是统一续费批次")
}
if sourceBatchNo != "" && strings.TrimSpace(card.OutboundBatchNo) != sourceBatchNo {
return nil, gerror.Newf("ICCID不属于来源出库批次:%s", iccid)
}
if currentPackageCode == "" {
currentPackageCode = strings.TrimSpace(card.CurrentPackageCode)
}
if currentPackageCode != strings.TrimSpace(card.CurrentPackageCode) {
return nil, gerror.New("当前批次下存在多个套餐,不能统一续费")
}
if customerAgentId == 0 {
customerAgentId = card.DeptId
}
if customerAgentId != card.DeptId {
return nil, gerror.New("当前批次下存在多个客户,不能统一续费")
}
}
customer, err := s.loadTargetDept(ctx, tx, customerAgentId, in.Operator, true)
if err != nil {
return nil, err
}
targetPackageCode := strings.TrimSpace(in.PackageCode)
if targetPackageCode == "" {
targetPackageCode = currentPackageCode
}
currentPkg, err := s.loadPackage(ctx, tx, currentPackageCode)
if err != nil {
return nil, err
}
if !renewPackageAllowed(currentPkg, targetPackageCode) {
return nil, gerror.New("续费套餐不在当前套餐可续费范围内")
}
pkg := currentPkg
if targetPackageCode != currentPackageCode {
pkg, err = s.loadPackage(ctx, tx, targetPackageCode)
}
if err != nil {
return nil, err
}
durationUnit, durationValue, durationMonths, giftDurationValue, giftDurationMonths := normalizeRenewPackageDuration(in)
if durationMonths <= 0 {
return nil, gerror.New("续费时长必须大于0")
}
amount, err := parseAmount(in.Amount)
if err != nil {
return nil, err
}
quantity := len(iccids)
unitPrice := 0.0
if quantity > 0 {
unitPrice = roundAmount(amount / float64(quantity))
}
firstBaseExpireAt := renewBaseExpireTime(cardsByIccid[iccids[0]], latestBindsByIccid[iccids[0]])
minBeginAt := renewPeriodBeginAt(firstBaseExpireAt, in.PackageBeginTime)
maxEndAt := firstBaseExpireAt
for _, iccid := range iccids {
baseExpireAt := renewBaseExpireTime(cardsByIccid[iccid], latestBindsByIccid[iccid])
periodBegin := renewPeriodBeginAt(baseExpireAt, in.PackageBeginTime)
if minBeginAt == nil || (periodBegin != nil && periodBegin.Before(minBeginAt)) {
minBeginAt = periodBegin
}
endAt := addMonths(periodBegin, durationMonths+giftDurationMonths)
if maxEndAt == nil || (endAt != nil && endAt.After(maxEndAt)) {
maxEndAt = endAt
}
}
if in.PackageEndTime != nil && !sameRenewTime(maxEndAt, in.PackageEndTime) {
return nil, gerror.New("续费结束日期与续费时长不一致")
}
orderSn, err := batchimport.NextRandomNo(orderNoPrefix)
if err != nil {
return nil, err
}
orderInput := &CreatePackageSubscriptionInput{
BatchNo: in.BatchNo,
CustomerAgentId: customerAgentId,
PackageCode: targetPackageCode,
PackageBeginTime: minBeginAt,
DurationMonths: durationMonths,
GiftDurationMonths: giftDurationMonths,
DurationDisplayValue: durationValue,
GiftDurationDisplayValue: giftDurationValue,
DurationUnit: durationUnit,
Amount: in.Amount,
PayType: in.PayType,
PayStatus: in.PayStatus,
BalanceAmount: in.BalanceAmount,
OfflinePaidAmount: in.OfflinePaidAmount,
PayTime: in.PayTime,
OfflineContractCompleted: in.OfflineContractCompleted,
BuyerNote: in.BuyerNote,
AdminNote: in.AdminNote,
BusinessType: consts.PackageBusinessTypeRenew,
OrderRemark: chooseString(in.OrderRemark, "卡续费业务订单载体"),
BindRemark: in.BindRemark,
Operator: in.Operator,
}
orderId, err := s.insertOrder(ctx, tx, orderInput, orderSn, amount, now)
if err != nil {
return nil, err
}
if err = s.insertOrderItem(ctx, tx, orderInput, orderId, orderSn, pkg, amount, unitPrice, quantity, minBeginAt, maxEndAt, now); err != nil {
return nil, err
}
bindRows := make([]do.TSimCardBindPkg, 0, quantity)
newBindsByIccid := make(map[string]*entity.TSimCardBindPkg, quantity)
for _, iccid := range iccids {
card := cardsByIccid[iccid]
latestBind := latestBindsByIccid[iccid]
periodBegin := renewPeriodBeginAt(renewBaseExpireTime(card, latestBind), in.PackageBeginTime)
periodEnd := addMonths(periodBegin, durationMonths+giftDurationMonths)
_, effectiveStatus := calcPackageStatuses(now, periodBegin, periodEnd)
subscriptionNo, err := batchimport.NextRandomNo(subscriptionNoPrefix)
if err != nil {
return nil, err
}
row := do.TSimCardBindPkg{
SubscriptionNo: subscriptionNo,
BatchNo: in.BatchNo,
Iccid: iccid,
Msisdn: strings.TrimSpace(card.Msisdn),
DeptId: fmt.Sprintf("%d", customerAgentId),
PackageType: pkg.PackageType,
PackageCode: pkg.PackageCode,
PreviousPackageCode: currentPackageCode,
BusinessType: consts.PackageBusinessTypeRenew,
EffectiveStatus: effectiveStatus,
HandleTime: now,
EffectiveTime: periodBegin,
ExpectedExpireTime: periodEnd,
DurationMonths: durationMonths,
GiftDurationMonths: giftDurationMonths,
OrderCode: orderSn,
Remark: strings.TrimSpace(in.BindRemark),
TenantId: in.Operator.TenantId,
CreatedDept: in.Operator.DeptId,
CreatedBy: in.Operator.UserId,
CreatedAt: now,
UpdatedBy: in.Operator.UserId,
UpdatedAt: now,
}
bindRows = append(bindRows, row)
newBindsByIccid[iccid] = &entity.TSimCardBindPkg{
SubscriptionNo: subscriptionNo,
BatchNo: in.BatchNo,
Iccid: iccid,
Msisdn: strings.TrimSpace(card.Msisdn),
DeptId: fmt.Sprintf("%d", customerAgentId),
PackageType: pkg.PackageType,
PackageCode: pkg.PackageCode,
PreviousPackageCode: currentPackageCode,
BusinessType: consts.PackageBusinessTypeRenew,
EffectiveStatus: effectiveStatus,
HandleTime: now,
EffectiveTime: periodBegin,
ExpectedExpireTime: periodEnd,
DurationMonths: durationMonths,
GiftDurationMonths: giftDurationMonths,
OrderCode: orderSn,
Remark: strings.TrimSpace(in.BindRemark),
TenantId: in.Operator.TenantId,
CreatedDept: in.Operator.DeptId,
CreatedBy: in.Operator.UserId,
CreatedAt: now,
UpdatedBy: in.Operator.UserId,
UpdatedAt: now,
}
}
if err = s.InsertCardPackageBinds(ctx, tx, bindRows); err != nil {
return nil, err
}
out := &RenewCurrentPackageOutput{
Iccids: iccids,
OrderId: orderId,
OrderSn: orderSn,
Customer: customer,
Package: pkg,
CardsByIccid: cardsByIccid,
LatestBindsByIccid: latestBindsByIccid,
NewBindsByIccid: newBindsByIccid,
BindRows: bindRows,
RenewTime: now,
PackageCode: targetPackageCode,
Amount: amount,
UnitPrice: unitPrice,
Quantity: quantity,
}
if err = s.UpdateCardsAfterRenew(ctx, tx, out, in.Operator.UserId); err != nil {
return nil, err
}
return out, nil
}
func renewExpireAfterAllowedWindow(expireAt, now *gtime.Time) bool {
if expireAt == nil {
return false
}
if now == nil {
now = gtime.Now()
}
return expireAt.After(now.AddDate(0, 6, 0))
}
func sameRenewTime(left, right *gtime.Time) bool {
switch {
case left == nil && right == nil:
return true
case left == nil || right == nil:
return false
default:
return !left.Before(right) && !left.After(right)
}
}
func renewPeriodBeginAt(latest, requested *gtime.Time) *gtime.Time {
if requested != nil {
return renewDayEndAt(requested)
}
return renewBeginDateFromExpire(latest)
}
func renewBeginDateFromExpire(expireAt *gtime.Time) *gtime.Time {
if expireAt == nil {
return nil
}
return renewDayEndAt(expireAt)
}
func renewBaseExpireTime(card *entity.TSimCardInfo, bind *entity.TSimCardBindPkg) *gtime.Time {
var expireAt *gtime.Time
if bind != nil {
expireAt = laterRenewExpireTime(expireAt, bind.ExpectedExpireTime)
}
if card != nil {
expireAt = laterRenewExpireTime(expireAt, card.FlowPkgEndAt)
expireAt = laterRenewExpireTime(expireAt, card.FlowPkgPresentAt)
expireAt = laterRenewExpireTime(expireAt, card.FlowPkgRealPresentAt)
}
return renewDayEndAt(expireAt)
}
func laterRenewExpireTime(current, candidate *gtime.Time) *gtime.Time {
if candidate == nil || candidate.IsZero() {
return current
}
if current == nil || current.IsZero() || candidate.After(current) {
return candidate
}
return current
}
func renewDayEndAt(t *gtime.Time) *gtime.Time {
if t == nil || t.IsZero() {
return nil
}
tt := t.Time
return gtime.NewFromTime(time.Date(tt.Year(), tt.Month(), tt.Day(), 23, 59, 59, 0, tt.Location()))
}
func renewPackageAllowed(currentPkg *entity.TSimFlowPackageInfo, targetPackageCode string) bool {
targetPackageCode = strings.TrimSpace(targetPackageCode)
if currentPkg == nil || targetPackageCode == "" {
return false
}
var codes []string
if currentPkg.RenewablePackageCodes != nil {
_ = currentPkg.RenewablePackageCodes.Scan(&codes)
}
for _, code := range uniqueStrings(codes) {
if strings.TrimSpace(code) == targetPackageCode {
return true
}
}
return false
}
func normalizeRenewPackageDuration(in *RenewCurrentPackageInput) (durationUnit string, durationValue int, durationMonths int, giftDurationValue int, giftDurationMonths int) {
durationUnit = consts.RenewDurationUnitYear
if in == nil {
return
}
durationValue = in.DurationDisplayValue
durationMonths = in.DurationMonths
if durationValue > 0 {
// Renewal duration is always expressed in years. The database keeps
// the resulting subscription duration in months for lifecycle logic.
durationMonths = packageduration.RenewMonths(durationValue)
}
giftDurationMonths = in.GiftDurationMonths
giftDurationValue = in.GiftDurationDisplayValue
if giftDurationValue <= 0 {
giftDurationValue = giftDurationMonths
}
return
}
func validateRenewCardStatus(iccid string, card *entity.TSimCardInfo) error {
if card == nil {
return gerror.Newf("ICCID不存在正式卡记录:%s", iccid)
}
if card.BossCardStatus == consts.V3ApiSimBossCardStatusStopped {
return gerror.Newf("ICCID当前为停机状态,不能续费:%s", iccid)
}
return nil
}
func validateRenewInput(in *RenewCurrentPackageInput) error {
if in == nil {
return gerror.New("续费参数不能为空")
}
if strings.TrimSpace(in.BatchNo) == "" {
return gerror.New("续费批次号不能为空")
}
if len(in.Iccids) == 0 {
return gerror.New("续费卡不能为空")
}
if in.DurationMonths <= 0 && in.DurationDisplayValue <= 0 {
return gerror.New("续费时长必须大于0")
}
if in.GiftDurationMonths < 0 {
return gerror.New("赠送时长不能小于0")
}
payType := in.PayType
if payType == 0 {
payType = consts.PayTypeOffline
}
if payType == consts.PayTypeOffline && !in.OfflineContractCompleted {
return gerror.New("请确认线下合同、销售和走账已完成")
}
return nil
}
func chooseString(primary, fallback string) string {
primary = strings.TrimSpace(primary)
if primary != "" {
return primary
}
return strings.TrimSpace(fallback)
}