420 lines
14 KiB
Go
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)
|
|
}
|