chore: 并入 iotlcm-admin-svr 历史到 admin-svr/(原提交 49818c6)
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
package cardpackage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
func (s *Service) UpdateCardsAfterRenew(ctx context.Context, tx gdb.TX, renew *RenewCurrentPackageOutput, userId int64) error {
|
||||
if renew == nil || len(renew.NewBindsByIccid) == 0 {
|
||||
return nil
|
||||
}
|
||||
now := gtime.Now()
|
||||
for _, iccid := range renew.Iccids {
|
||||
card := renew.CardsByIccid[iccid]
|
||||
if card == nil {
|
||||
return gerror.Newf("ICCID卡信息不存在:%s", iccid)
|
||||
}
|
||||
bind := renew.NewBindsByIccid[iccid]
|
||||
if bind == nil {
|
||||
return gerror.Newf("ICCID续费订购记录不存在:%s", iccid)
|
||||
}
|
||||
packageStatus, _ := calcPackageStatuses(now, bind.EffectiveTime, bind.ExpectedExpireTime)
|
||||
result, err := tx.Ctx(ctx).Model(dao.TSimCardInfo.Table()).
|
||||
Where(dao.TSimCardInfo.Columns().Iccid, iccid).
|
||||
Data(do.TSimCardInfo{
|
||||
FlowPkgEndAt: bind.ExpectedExpireTime,
|
||||
PackageStatus: packageStatus,
|
||||
UpdatedBy: userId,
|
||||
UpdatedAt: now,
|
||||
}).
|
||||
Update()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "更新续费后卡套餐到期时间失败")
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "读取续费后卡信息更新数量失败")
|
||||
}
|
||||
if affected != 1 {
|
||||
return gerror.Newf("ICCID卡信息已变化,请刷新后重试:%s", iccid)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cardpackage
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
)
|
||||
|
||||
func TestValidateRenewCardStatusRejectsStoppedCard(t *testing.T) {
|
||||
err := validateRenewCardStatus("89860401788404184188", &entity.TSimCardInfo{
|
||||
BossCardStatus: consts.V3ApiSimBossCardStatusStopped,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("validateRenewCardStatus() error = nil, want stopped card error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "当前为停机状态") {
|
||||
t.Fatalf("validateRenewCardStatus() error = %q, want stopped card error", err.Error())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package cardpackage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"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"
|
||||
)
|
||||
|
||||
const cardPackageBindInsertBatchSize = 500
|
||||
|
||||
type Repository struct{}
|
||||
|
||||
func NewRepository() *Repository {
|
||||
return &Repository{}
|
||||
}
|
||||
|
||||
func (r *Repository) LockCardsByIccids(ctx context.Context, tx gdb.TX, iccids []string) (map[string]*entity.TSimCardInfo, error) {
|
||||
result := make(map[string]*entity.TSimCardInfo)
|
||||
uniqueIccids := uniqueStrings(iccids)
|
||||
if len(uniqueIccids) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var list []*entity.TSimCardInfo
|
||||
if err := tx.Ctx(ctx).Model(dao.TSimCardInfo.Table()).
|
||||
WhereIn(dao.TSimCardInfo.Columns().Iccid, uniqueIccids).
|
||||
LockUpdate().
|
||||
Scan(&list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range list {
|
||||
if item != nil && item.Iccid != "" {
|
||||
result[item.Iccid] = item
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) LockCurrentActiveBindsByIccids(ctx context.Context, tx gdb.TX, iccids []string) (map[string]*entity.TSimCardBindPkg, error) {
|
||||
result := make(map[string]*entity.TSimCardBindPkg)
|
||||
priorities := make(map[string]int)
|
||||
uniqueIccids := uniqueStrings(iccids)
|
||||
if len(uniqueIccids) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var list []*entity.TSimCardBindPkg
|
||||
if err := tx.Ctx(ctx).Model(dao.TSimCardBindPkg.Table()).
|
||||
WhereIn(dao.TSimCardBindPkg.Columns().Iccid, uniqueIccids).
|
||||
WhereIn(dao.TSimCardBindPkg.Columns().EffectiveStatus, []int{consts.EffectiveStatusPending, consts.EffectiveStatusEffective}).
|
||||
OrderDesc(dao.TSimCardBindPkg.Columns().EffectiveStatus).
|
||||
OrderAsc(dao.TSimCardBindPkg.Columns().EffectiveTime).
|
||||
OrderAsc(dao.TSimCardBindPkg.Columns().Id).
|
||||
LockUpdate().
|
||||
Scan(&list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range list {
|
||||
if item == nil || item.Iccid == "" {
|
||||
continue
|
||||
}
|
||||
priority := bindPriority(item.EffectiveStatus)
|
||||
currentPriority, exists := priorities[item.Iccid]
|
||||
if exists && priority < currentPriority {
|
||||
continue
|
||||
}
|
||||
if exists && priority == currentPriority {
|
||||
result[item.Iccid] = nil
|
||||
continue
|
||||
}
|
||||
priorities[item.Iccid] = priority
|
||||
result[item.Iccid] = item
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) LockLatestBindsByIccids(ctx context.Context, tx gdb.TX, iccids []string) (map[string]*entity.TSimCardBindPkg, error) {
|
||||
result := make(map[string]*entity.TSimCardBindPkg)
|
||||
uniqueIccids := uniqueStrings(iccids)
|
||||
if len(uniqueIccids) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var list []*entity.TSimCardBindPkg
|
||||
if err := tx.Ctx(ctx).Model(dao.TSimCardBindPkg.Table()).
|
||||
WhereIn(dao.TSimCardBindPkg.Columns().Iccid, uniqueIccids).
|
||||
WhereIn(dao.TSimCardBindPkg.Columns().PackageType, []int{101, 102}).
|
||||
WhereIn(dao.TSimCardBindPkg.Columns().EffectiveStatus, []int{
|
||||
consts.EffectiveStatusPending,
|
||||
consts.EffectiveStatusEffective,
|
||||
consts.EffectiveStatusExpired,
|
||||
}).
|
||||
OrderDesc(dao.TSimCardBindPkg.Columns().ExpectedExpireTime).
|
||||
OrderDesc(dao.TSimCardBindPkg.Columns().Id).
|
||||
LockUpdate().
|
||||
Scan(&list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
effectiveCount := make(map[string]int)
|
||||
for _, item := range list {
|
||||
if item == nil || item.Iccid == "" {
|
||||
continue
|
||||
}
|
||||
iccid := strings.TrimSpace(item.Iccid)
|
||||
if item.EffectiveStatus == consts.EffectiveStatusEffective {
|
||||
effectiveCount[iccid]++
|
||||
if effectiveCount[iccid] > 1 {
|
||||
return nil, gerror.Newf("同一张卡存在多个生效中的套餐订购记录:%s", iccid)
|
||||
}
|
||||
}
|
||||
if _, exists := result[iccid]; exists {
|
||||
continue
|
||||
}
|
||||
result[iccid] = item
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) ActiveBindCountsByOrderCodes(ctx context.Context, tx gdb.TX, orderCodes []string) (map[string]int, error) {
|
||||
result := make(map[string]int)
|
||||
uniqueOrderCodes := uniqueStrings(orderCodes)
|
||||
if len(uniqueOrderCodes) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var list []struct {
|
||||
OrderCode string `orm:"order_code"`
|
||||
Count int `orm:"count"`
|
||||
}
|
||||
err := tx.Ctx(ctx).Model(dao.TSimCardBindPkg.Table()).
|
||||
Fields(dao.TSimCardBindPkg.Columns().OrderCode, "COUNT(1) AS count").
|
||||
WhereIn(dao.TSimCardBindPkg.Columns().OrderCode, uniqueOrderCodes).
|
||||
Group(dao.TSimCardBindPkg.Columns().OrderCode).
|
||||
Scan(&list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range list {
|
||||
result[item.OrderCode] = item.Count
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) LoadTargetDept(ctx context.Context, tx gdb.TX, deptId int64, tenantId string) (*entity.SysDept, error) {
|
||||
var customer *entity.SysDept
|
||||
mod := tx.Ctx(ctx).Model(dao.SysDept.Table()).
|
||||
Where(dao.SysDept.Columns().DeptId, deptId)
|
||||
if tenantId != "" {
|
||||
mod = mod.Where(dao.SysDept.Columns().TenantId, tenantId)
|
||||
}
|
||||
if err := mod.Scan(&customer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
func (r *Repository) LoadPackage(ctx context.Context, tx gdb.TX, packageCode string) (*entity.TSimFlowPackageInfo, error) {
|
||||
var pkg *entity.TSimFlowPackageInfo
|
||||
err := tx.Ctx(ctx).Model(dao.TSimFlowPackageInfo.Table()).
|
||||
Where(dao.TSimFlowPackageInfo.Columns().PackageCode, strings.TrimSpace(packageCode)).
|
||||
Scan(&pkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
func (r *Repository) InsertOrder(ctx context.Context, tx gdb.TX, data do.TOrder) (int64, error) {
|
||||
return tx.Ctx(ctx).Model(dao.TOrder.Table()).Data(data).InsertAndGetId()
|
||||
}
|
||||
|
||||
func (r *Repository) InsertOrderItem(ctx context.Context, tx gdb.TX, data do.TOrderItem) error {
|
||||
_, err := tx.Ctx(ctx).Model(dao.TOrderItem.Table()).Data(data).Insert()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Repository) InsertCardPackageBinds(ctx context.Context, tx gdb.TX, rows []do.TSimCardBindPkg) error {
|
||||
if len(rows) == 0 {
|
||||
return nil
|
||||
}
|
||||
for start := 0; start < len(rows); start += cardPackageBindInsertBatchSize {
|
||||
end := start + cardPackageBindInsertBatchSize
|
||||
if end > len(rows) {
|
||||
end = len(rows)
|
||||
}
|
||||
if _, err := tx.Ctx(ctx).Model(dao.TSimCardBindPkg.Table()).Data(rows[start:end]).Insert(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteBindsBySubscriptionNos(ctx context.Context, tx gdb.TX, subscriptionNos []string) (int64, error) {
|
||||
uniqueNos := uniqueStrings(subscriptionNos)
|
||||
if len(uniqueNos) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
result, err := tx.Ctx(ctx).Model(dao.TSimCardBindPkg.Table()).
|
||||
WhereIn(dao.TSimCardBindPkg.Columns().SubscriptionNo, uniqueNos).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
func uniqueStrings(list []string) []string {
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
seen := make(map[string]struct{}, len(list))
|
||||
result := make([]string, 0, len(list))
|
||||
for _, item := range list {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[item]; ok {
|
||||
continue
|
||||
}
|
||||
seen[item] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
func bindPriority(status int) int {
|
||||
switch status {
|
||||
case consts.EffectiveStatusEffective:
|
||||
return 2
|
||||
case consts.EffectiveStatusPending:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cardpackage
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCardPackageBindChunkSizeSupportsFiftyThousandCards(t *testing.T) {
|
||||
const totalCards = 50000
|
||||
if cardPackageBindInsertBatchSize <= 0 || cardPackageBindInsertBatchSize > 500 {
|
||||
t.Fatalf("cardPackageBindInsertBatchSize = %d, want 1..500", cardPackageBindInsertBatchSize)
|
||||
}
|
||||
if chunks := (totalCards + cardPackageBindInsertBatchSize - 1) / cardPackageBindInsertBatchSize; chunks != 100 {
|
||||
t.Fatalf("bind insert chunks = %d, want 100", chunks)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,548 @@
|
||||
package cardpackage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"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/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/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
const (
|
||||
orderNoPrefix = "SO"
|
||||
subscriptionNoPrefix = "SUB"
|
||||
)
|
||||
|
||||
func (s *Service) CreatePackageSubscription(ctx context.Context, tx gdb.TX, in *CreatePackageSubscriptionInput) (*CreatePackageSubscriptionOutput, error) {
|
||||
if err := validateSubscribeInput(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customer, err := s.loadTargetDept(ctx, tx, in.CustomerAgentId, in.Operator, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg, err := s.loadPackage(ctx, tx, strings.TrimSpace(in.PackageCode))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := gtime.Now()
|
||||
periodBegin := in.PackageBeginTime
|
||||
periodEnd := packageEndAt(periodBegin, in.DurationMonths+in.GiftDurationMonths, in.EndAtPreviousMonthEnd)
|
||||
packageStatus := CalcPackageStatus(now, periodBegin, periodEnd)
|
||||
effectiveStatus := CalcEffectiveStatus(now, periodBegin, periodEnd)
|
||||
amount, err := parseAmount(in.Amount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quantity := len(in.Cards)
|
||||
unitPrice := 0.0
|
||||
if quantity > 0 {
|
||||
unitPrice = roundAmount(amount / float64(quantity))
|
||||
}
|
||||
businessType := in.BusinessType
|
||||
if businessType == 0 {
|
||||
businessType = consts.PackageBusinessTypeSubscribe
|
||||
}
|
||||
|
||||
orderSn, err := batchimport.NextRandomNo(orderNoPrefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orderId, err := s.insertOrder(ctx, tx, in, orderSn, amount, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = s.insertOrderItem(ctx, tx, in, orderId, orderSn, pkg, amount, unitPrice, quantity, periodBegin, periodEnd, now); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bindRows := make([]do.TSimCardBindPkg, 0, quantity)
|
||||
bindsByIccid := make(map[string]*entity.TSimCardBindPkg, quantity)
|
||||
for _, card := range in.Cards {
|
||||
iccid := strings.TrimSpace(card.Iccid)
|
||||
if iccid == "" {
|
||||
return nil, gerror.New("订购卡ICCID不能为空")
|
||||
}
|
||||
subscriptionNo, err := batchimport.NextRandomNo(subscriptionNoPrefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bindRow := do.TSimCardBindPkg{
|
||||
SubscriptionNo: subscriptionNo,
|
||||
BatchNo: in.BatchNo,
|
||||
Iccid: iccid,
|
||||
Msisdn: strings.TrimSpace(card.Msisdn),
|
||||
DeptId: fmt.Sprintf("%d", in.CustomerAgentId),
|
||||
PackageType: pkg.PackageType,
|
||||
PackageCode: pkg.PackageCode,
|
||||
PreviousPackageCode: strings.TrimSpace(in.PreviousPackageCode),
|
||||
BusinessType: businessType,
|
||||
EffectiveStatus: effectiveStatus,
|
||||
HandleTime: now,
|
||||
EffectiveTime: periodBegin,
|
||||
ExpectedExpireTime: periodEnd,
|
||||
DurationMonths: in.DurationMonths,
|
||||
GiftDurationMonths: in.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, bindRow)
|
||||
bindsByIccid[iccid] = &entity.TSimCardBindPkg{
|
||||
SubscriptionNo: subscriptionNo,
|
||||
BatchNo: in.BatchNo,
|
||||
Iccid: iccid,
|
||||
Msisdn: strings.TrimSpace(card.Msisdn),
|
||||
DeptId: fmt.Sprintf("%d", in.CustomerAgentId),
|
||||
PackageType: pkg.PackageType,
|
||||
PackageCode: pkg.PackageCode,
|
||||
PreviousPackageCode: strings.TrimSpace(in.PreviousPackageCode),
|
||||
BusinessType: businessType,
|
||||
EffectiveStatus: effectiveStatus,
|
||||
HandleTime: now,
|
||||
EffectiveTime: periodBegin,
|
||||
ExpectedExpireTime: periodEnd,
|
||||
DurationMonths: in.DurationMonths,
|
||||
GiftDurationMonths: in.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 !in.SkipBindInsert {
|
||||
if err = s.InsertCardPackageBinds(ctx, tx, bindRows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &CreatePackageSubscriptionOutput{
|
||||
OrderId: orderId,
|
||||
OrderSn: orderSn,
|
||||
Customer: customer,
|
||||
Package: pkg,
|
||||
PackageBeginAt: periodBegin,
|
||||
PackageEndAt: periodEnd,
|
||||
SubscribeTime: now,
|
||||
PackageStatus: packageStatus,
|
||||
EffectiveStatus: effectiveStatus,
|
||||
Amount: amount,
|
||||
UnitPrice: unitPrice,
|
||||
Quantity: quantity,
|
||||
BindsByIccid: bindsByIccid,
|
||||
BindRows: bindRows,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) InsertCardPackageBinds(ctx context.Context, tx gdb.TX, rows []do.TSimCardBindPkg) error {
|
||||
if err := s.repo.InsertCardPackageBinds(ctx, tx, rows); err != nil {
|
||||
return gerror.Wrap(err, "写入卡套餐绑定失败")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSubscribeInput(in *CreatePackageSubscriptionInput) error {
|
||||
if in == nil {
|
||||
return gerror.New("订购参数不能为空")
|
||||
}
|
||||
if strings.TrimSpace(in.BatchNo) == "" {
|
||||
return gerror.New("订购批次号不能为空")
|
||||
}
|
||||
if len(in.Cards) == 0 {
|
||||
return gerror.New("订购卡不能为空")
|
||||
}
|
||||
if in.CustomerAgentId <= 0 {
|
||||
return gerror.New("客户不能为空")
|
||||
}
|
||||
if strings.TrimSpace(in.PackageCode) == "" {
|
||||
return gerror.New("套餐不能为空")
|
||||
}
|
||||
if in.PackageBeginTime == nil {
|
||||
return gerror.New("套餐开始时间不能为空")
|
||||
}
|
||||
if in.DurationMonths <= 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 (s *Service) loadTargetDept(ctx context.Context, tx gdb.TX, deptId int64, operator batchimport.OperatorContext, allowDescendant bool) (*entity.SysDept, error) {
|
||||
if operator.DeptId == 0 {
|
||||
return nil, gerror.New("当前用户未绑定机构")
|
||||
}
|
||||
|
||||
customer, err := s.repo.LoadTargetDept(ctx, tx, deptId, operator.TenantId)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "查询客户/机构失败")
|
||||
}
|
||||
if customer == nil || customer.DeptId == 0 {
|
||||
return nil, gerror.New("客户/机构不存在或不在可操作范围内")
|
||||
}
|
||||
if customer.DeptId == operator.DeptId || customer.ParentId == operator.DeptId {
|
||||
return customer, nil
|
||||
}
|
||||
if allowDescendant && deptAncestorsContain(customer.Ancestors, operator.DeptId) {
|
||||
return customer, nil
|
||||
}
|
||||
if customer.DeptId != operator.DeptId && customer.ParentId != operator.DeptId {
|
||||
return nil, gerror.New("客户/机构不存在或不在可操作范围内")
|
||||
}
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
func deptAncestorsContain(ancestors string, deptId int64) bool {
|
||||
if deptId <= 0 {
|
||||
return false
|
||||
}
|
||||
target := strconv.FormatInt(deptId, 10)
|
||||
for _, item := range strings.Split(strings.Trim(ancestors, ","), ",") {
|
||||
if strings.TrimSpace(item) == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Service) loadPackage(ctx context.Context, tx gdb.TX, packageCode string) (*entity.TSimFlowPackageInfo, error) {
|
||||
pkg, err := s.repo.LoadPackage(ctx, tx, packageCode)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "查询套餐失败")
|
||||
}
|
||||
if pkg == nil {
|
||||
return nil, gerror.New("套餐不存在")
|
||||
}
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
func (s *Service) insertOrder(ctx context.Context, tx gdb.TX, in *CreatePackageSubscriptionInput, orderSn string, amount float64, now *gtime.Time) (int64, error) {
|
||||
remark := strings.TrimSpace(in.OrderRemark)
|
||||
if remark == "" {
|
||||
remark = "顶级出库划拨业务订单载体"
|
||||
}
|
||||
payment := normalizeSubscriptionPayment(in, amount, now)
|
||||
orderId, err := s.repo.InsertOrder(ctx, tx, do.TOrder{
|
||||
OrderSn: orderSn,
|
||||
ParentOrderId: 0,
|
||||
ParentOrderSn: "",
|
||||
CustomerType: consts.CustomerTypeAgent,
|
||||
CustomerAgentId: in.CustomerAgentId,
|
||||
EndUserId: 0,
|
||||
AgentId: in.Operator.DeptId,
|
||||
OrderType: orderTypeForBusinessType(in.BusinessType),
|
||||
OrderStatus: consts.OrderStatusCompleted,
|
||||
ShippingStatus: 0,
|
||||
ReturnStatus: 0,
|
||||
Consignee: "",
|
||||
Mobile: "",
|
||||
Email: "",
|
||||
Address: "",
|
||||
BuyerNote: strings.TrimSpace(in.BuyerNote),
|
||||
AdminNote: strings.TrimSpace(in.AdminNote),
|
||||
Remark: remark,
|
||||
ShippingMethod: 3,
|
||||
LogisticsId: 0,
|
||||
LogisticsName: "",
|
||||
ShippingTypeId: 0,
|
||||
ShippingTypeName: "",
|
||||
TrackingNo: "",
|
||||
PayType: payment.PayType,
|
||||
PayStatus: payment.PayStatus,
|
||||
PayOrderSn: "",
|
||||
PayTime: payment.PayTime,
|
||||
TotalAmount: amount,
|
||||
ProductAmount: amount,
|
||||
Balance: payment.BalanceAmount,
|
||||
PaidAmount: amount,
|
||||
OfflinePaidAmount: payment.OfflinePaidAmount,
|
||||
IsNeedCommission: 0,
|
||||
DistributionStatus: 0,
|
||||
TenantId: in.Operator.TenantId,
|
||||
CreatedDept: in.Operator.DeptId,
|
||||
CreatedBy: in.Operator.UserId,
|
||||
CreatedAt: now,
|
||||
UpdatedBy: in.Operator.UserId,
|
||||
UpdatedAt: now,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "创建订单失败")
|
||||
}
|
||||
return orderId, nil
|
||||
}
|
||||
|
||||
func orderTypeForBusinessType(businessType int) int {
|
||||
switch businessType {
|
||||
case consts.PackageBusinessTypeRenew:
|
||||
return consts.OrderTypeCardRenew
|
||||
case consts.PackageBusinessTypeChange:
|
||||
return consts.OrderTypeCardChange
|
||||
default:
|
||||
return consts.OrderTypeCardSubscribe
|
||||
}
|
||||
}
|
||||
|
||||
type subscriptionPayment struct {
|
||||
PayType int
|
||||
PayStatus int
|
||||
BalanceAmount float64
|
||||
OfflinePaidAmount float64
|
||||
PayTime *gtime.Time
|
||||
}
|
||||
|
||||
func normalizeSubscriptionPayment(in *CreatePackageSubscriptionInput, amount float64, now *gtime.Time) subscriptionPayment {
|
||||
payment := subscriptionPayment{
|
||||
PayType: consts.PayTypeOffline,
|
||||
PayStatus: consts.PayStatusPaid,
|
||||
OfflinePaidAmount: amount,
|
||||
PayTime: now,
|
||||
}
|
||||
if in == nil {
|
||||
return payment
|
||||
}
|
||||
if in.PayType > 0 {
|
||||
payment.PayType = in.PayType
|
||||
}
|
||||
if in.PayStatus > 0 {
|
||||
payment.PayStatus = in.PayStatus
|
||||
}
|
||||
if in.PayTime != nil {
|
||||
payment.PayTime = in.PayTime
|
||||
}
|
||||
switch payment.PayType {
|
||||
case consts.PayTypeBalance:
|
||||
payment.BalanceAmount = roundAmount(in.BalanceAmount)
|
||||
if payment.BalanceAmount == 0 && amount > 0 {
|
||||
payment.BalanceAmount = amount
|
||||
}
|
||||
payment.OfflinePaidAmount = 0
|
||||
default:
|
||||
payment.OfflinePaidAmount = roundAmount(in.OfflinePaidAmount)
|
||||
if payment.OfflinePaidAmount == 0 && amount > 0 {
|
||||
payment.OfflinePaidAmount = amount
|
||||
}
|
||||
payment.BalanceAmount = roundAmount(in.BalanceAmount)
|
||||
}
|
||||
return payment
|
||||
}
|
||||
|
||||
func (s *Service) insertOrderItem(ctx context.Context, tx gdb.TX, in *CreatePackageSubscriptionInput, orderId int64, orderSn string, pkg *entity.TSimFlowPackageInfo, amount, unitPrice float64, quantity int, beginAt, endAt *gtime.Time, now *gtime.Time) error {
|
||||
durationLabel := strings.TrimSpace(in.DurationDisplayLabel)
|
||||
if durationLabel == "" {
|
||||
durationLabel = fmt.Sprintf("%d个月", in.DurationMonths)
|
||||
}
|
||||
if strings.TrimSpace(in.DurationDisplayLabel) == "" && in.DurationDisplayValue > 0 && strings.TrimSpace(in.DurationUnit) != "" {
|
||||
durationLabel = fmt.Sprintf("%d%s", in.DurationDisplayValue, consts.RenewDurationUnitText(strings.TrimSpace(in.DurationUnit)))
|
||||
}
|
||||
skuData := marshalJSON([]g.Map{
|
||||
{"skuName": "套餐", "skuValue": pkg.PackageName, "packageCode": pkg.PackageCode, "packageType": pkg.PackageType},
|
||||
{"skuName": "数量", "skuValue": fmt.Sprintf("%d", quantity)},
|
||||
{"skuName": "生效时间", "skuValue": formatTime(beginAt)},
|
||||
{"skuName": "时长", "skuValue": durationLabel},
|
||||
})
|
||||
extraSkuData := marshalJSON(g.Map{
|
||||
"outboundBatchNo": in.BatchNo,
|
||||
"businessBatchNo": in.BatchNo,
|
||||
"offlineContractCompleted": in.OfflineContractCompleted,
|
||||
"packageBeginTime": formatTime(beginAt),
|
||||
"packageEndTime": formatTime(endAt),
|
||||
"durationMonths": in.DurationMonths,
|
||||
"giftDurationMonths": in.GiftDurationMonths,
|
||||
"durationValue": in.DurationDisplayValue,
|
||||
"giftDurationValue": in.GiftDurationDisplayValue,
|
||||
"durationLabel": durationLabel,
|
||||
"giftDurationLabel": strings.TrimSpace(in.GiftDurationDisplayLabel),
|
||||
"durationUnit": strings.TrimSpace(in.DurationUnit),
|
||||
"amount": amount,
|
||||
})
|
||||
if err := s.repo.InsertOrderItem(ctx, tx, do.TOrderItem{
|
||||
OrderId: orderId,
|
||||
OrderSn: orderSn,
|
||||
CustomerType: consts.CustomerTypeAgent,
|
||||
CustomerAgentId: in.CustomerAgentId,
|
||||
EndUserId: 0,
|
||||
Price: unitPrice,
|
||||
Quantity: quantity,
|
||||
ProductId: pkg.Id,
|
||||
ProductName: pkg.PackageName,
|
||||
ProductSn: pkg.PackageCode,
|
||||
PicThumb: "",
|
||||
SkuId: 0,
|
||||
SkuData: skuData,
|
||||
DeliveryQuantity: 0,
|
||||
ProductType: consts.ProductTypeCard,
|
||||
IsGift: 0,
|
||||
AgentId: in.Operator.DeptId,
|
||||
PrepayPrice: 0,
|
||||
Commission: "",
|
||||
OriginPrice: unitPrice,
|
||||
ExtraSkuData: extraSkuData,
|
||||
PayMode: consts.PayModeYearMonth,
|
||||
ServiceBeginTime: beginAt,
|
||||
ServiceEndTime: endAt,
|
||||
TenantId: in.Operator.TenantId,
|
||||
CreatedDept: in.Operator.DeptId,
|
||||
CreatedBy: in.Operator.UserId,
|
||||
CreatedAt: now,
|
||||
UpdatedBy: in.Operator.UserId,
|
||||
UpdatedAt: now,
|
||||
}); err != nil {
|
||||
return gerror.Wrap(err, "创建订单项失败")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addMonths(t *gtime.Time, months int) *gtime.Time {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
if months <= 0 {
|
||||
return endOfDayAt(t.Time)
|
||||
}
|
||||
beginAt := t.Time
|
||||
targetMonth := time.Date(
|
||||
beginAt.Year(),
|
||||
beginAt.Month()+time.Month(months),
|
||||
1,
|
||||
beginAt.Hour(),
|
||||
beginAt.Minute(),
|
||||
beginAt.Second(),
|
||||
beginAt.Nanosecond(),
|
||||
beginAt.Location(),
|
||||
)
|
||||
day := beginAt.Day()
|
||||
lastDay := time.Date(
|
||||
targetMonth.Year(),
|
||||
targetMonth.Month()+1,
|
||||
0,
|
||||
beginAt.Hour(),
|
||||
beginAt.Minute(),
|
||||
beginAt.Second(),
|
||||
beginAt.Nanosecond(),
|
||||
beginAt.Location(),
|
||||
).Day()
|
||||
if day > lastDay {
|
||||
day = lastDay
|
||||
}
|
||||
return endOfDayAt(time.Date(
|
||||
targetMonth.Year(),
|
||||
targetMonth.Month(),
|
||||
day,
|
||||
beginAt.Hour(),
|
||||
beginAt.Minute(),
|
||||
beginAt.Second(),
|
||||
beginAt.Nanosecond(),
|
||||
beginAt.Location(),
|
||||
))
|
||||
}
|
||||
|
||||
func endOfDayAt(t time.Time) *gtime.Time {
|
||||
return gtime.NewFromTime(time.Date(
|
||||
t.Year(),
|
||||
t.Month(),
|
||||
t.Day(),
|
||||
23,
|
||||
59,
|
||||
59,
|
||||
0,
|
||||
t.Location(),
|
||||
))
|
||||
}
|
||||
|
||||
func packageEndAt(beginAt *gtime.Time, months int, previousMonthEnd bool) *gtime.Time {
|
||||
endAt := addMonths(beginAt, months)
|
||||
if !previousMonthEnd || endAt == nil {
|
||||
return endAt
|
||||
}
|
||||
return previousMonthEndAt(endAt)
|
||||
}
|
||||
|
||||
func previousMonthEndAt(t *gtime.Time) *gtime.Time {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
tt := t.Time
|
||||
return gtime.NewFromTime(time.Date(tt.Year(), tt.Month(), 0, 23, 59, 59, 0, tt.Location()))
|
||||
}
|
||||
|
||||
func CalcPackageStatus(now, beginAt, endAt *gtime.Time) int {
|
||||
if beginAt != nil && beginAt.After(now) {
|
||||
return consts.PackageStatusPending
|
||||
}
|
||||
if endAt != nil && !endAt.After(now) {
|
||||
return consts.PackageStatusExpired
|
||||
}
|
||||
return consts.PackageStatusEffective
|
||||
}
|
||||
|
||||
func calcPackageStatuses(now, beginAt, endAt *gtime.Time) (packageStatus, effectiveStatus int) {
|
||||
return CalcPackageStatus(now, beginAt, endAt), CalcEffectiveStatus(now, beginAt, endAt)
|
||||
}
|
||||
|
||||
func CalcEffectiveStatus(now, beginAt, endAt *gtime.Time) int {
|
||||
if beginAt != nil && beginAt.After(now) {
|
||||
return consts.EffectiveStatusPending
|
||||
}
|
||||
if endAt != nil && !endAt.After(now) {
|
||||
return consts.EffectiveStatusExpired
|
||||
}
|
||||
return consts.EffectiveStatusEffective
|
||||
}
|
||||
|
||||
func parseAmount(value string) (float64, error) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return 0, nil
|
||||
}
|
||||
amount, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return 0, gerror.New("订单金额格式不正确")
|
||||
}
|
||||
if amount < 0 {
|
||||
return 0, gerror.New("订单金额不能小于0")
|
||||
}
|
||||
return roundAmount(amount), nil
|
||||
}
|
||||
|
||||
func roundAmount(value float64) float64 {
|
||||
return math.Round(value*100) / 100
|
||||
}
|
||||
|
||||
func marshalJSON(value any) string {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package cardpackage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
func TestOrderTypeForBusinessType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
businessType int
|
||||
want int
|
||||
}{
|
||||
{name: "subscribe", businessType: consts.PackageBusinessTypeSubscribe, want: consts.OrderTypeCardSubscribe},
|
||||
{name: "renew", businessType: consts.PackageBusinessTypeRenew, want: consts.OrderTypeCardRenew},
|
||||
{name: "change", businessType: consts.PackageBusinessTypeChange, want: consts.OrderTypeCardChange},
|
||||
{name: "unknown defaults to subscribe", businessType: 99, want: consts.OrderTypeCardSubscribe},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if got := orderTypeForBusinessType(test.businessType); got != test.want {
|
||||
t.Fatalf("orderTypeForBusinessType(%d) = %d, want %d", test.businessType, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcPackageStatuses(t *testing.T) {
|
||||
now := gtime.NewFromStr("2026-10-27 12:00:00")
|
||||
tests := []struct {
|
||||
name string
|
||||
beginAt, endAt *gtime.Time
|
||||
wantPackage, wantBind int
|
||||
}{
|
||||
{
|
||||
name: "renew period already effective",
|
||||
beginAt: gtime.NewFromStr("2026-09-30 23:59:59"),
|
||||
endAt: gtime.NewFromStr("2027-09-30 23:59:59"),
|
||||
wantPackage: consts.PackageStatusEffective,
|
||||
wantBind: consts.EffectiveStatusEffective,
|
||||
},
|
||||
{
|
||||
name: "renew period starts in future",
|
||||
beginAt: gtime.NewFromStr("2026-11-01 23:59:59"),
|
||||
endAt: gtime.NewFromStr("2027-11-01 23:59:59"),
|
||||
wantPackage: consts.PackageStatusPending,
|
||||
wantBind: consts.EffectiveStatusPending,
|
||||
},
|
||||
{
|
||||
name: "renew period already expired",
|
||||
beginAt: gtime.NewFromStr("2025-09-30 23:59:59"),
|
||||
endAt: gtime.NewFromStr("2026-09-30 23:59:59"),
|
||||
wantPackage: consts.PackageStatusExpired,
|
||||
wantBind: consts.EffectiveStatusExpired,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
packageStatus, effectiveStatus := calcPackageStatuses(now, test.beginAt, test.endAt)
|
||||
if packageStatus != test.wantPackage || effectiveStatus != test.wantBind {
|
||||
t.Fatalf("calcPackageStatuses() = (%d, %d), want (%d, %d)", packageStatus, effectiveStatus, test.wantPackage, test.wantBind)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package cardpackage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"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/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
func (s *Service) TerminateCurrentPackage(ctx context.Context, tx gdb.TX, in *TerminateCurrentPackageInput) (*TerminateCurrentPackageOutput, error) {
|
||||
if in == nil || len(in.Iccids) == 0 {
|
||||
return nil, gerror.New("退订卡不能为空")
|
||||
}
|
||||
|
||||
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("部分卡信息已变化,请刷新后重试")
|
||||
}
|
||||
|
||||
bindsByIccid, err := s.repo.LockCurrentActiveBindsByIccids(ctx, tx, iccids)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "锁定当前订购失败")
|
||||
}
|
||||
if len(bindsByIccid) != len(iccids) {
|
||||
return nil, gerror.New("部分卡没有当前订购,不能退订")
|
||||
}
|
||||
|
||||
now := gtime.Now()
|
||||
sourceBatchNo := strings.TrimSpace(in.SourceBatchNo)
|
||||
packageCode := ""
|
||||
orderCodeSet := make(map[string]struct{}, len(iccids))
|
||||
orderUnsubCounts := make(map[string]int, len(iccids))
|
||||
logRows := make([]do.TSimBindPackageLogs, 0, len(iccids))
|
||||
subscriptionNos := make([]string, 0, len(iccids))
|
||||
|
||||
for _, iccid := range iccids {
|
||||
card := cardsByIccid[iccid]
|
||||
bind := bindsByIccid[iccid]
|
||||
if card == nil {
|
||||
return nil, gerror.Newf("ICCID不存在正式卡记录:%s", iccid)
|
||||
}
|
||||
if bind == nil {
|
||||
return nil, gerror.Newf("ICCID当前订购数据异常或不存在:%s", iccid)
|
||||
}
|
||||
if card.DeptId != in.Operator.DeptId && card.CreatedDept != in.Operator.DeptId {
|
||||
return nil, gerror.Newf("ICCID不属于当前机构:%s", iccid)
|
||||
}
|
||||
if strings.TrimSpace(card.CurrentPackageCode) == "" || card.PackageStatus == consts.PackageStatusNone {
|
||||
return nil, gerror.Newf("ICCID没有可退订套餐:%s", iccid)
|
||||
}
|
||||
if packageCode == "" {
|
||||
packageCode = strings.TrimSpace(bind.PackageCode)
|
||||
}
|
||||
if packageCode != strings.TrimSpace(bind.PackageCode) {
|
||||
return nil, gerror.New("当前批次下存在多个套餐,不能统一退订")
|
||||
}
|
||||
if strings.TrimSpace(card.CurrentPackageCode) != strings.TrimSpace(bind.PackageCode) {
|
||||
return nil, gerror.Newf("ICCID当前套餐与订购记录不一致:%s", iccid)
|
||||
}
|
||||
if sourceBatchNo != "" && strings.TrimSpace(bind.BatchNo) != sourceBatchNo {
|
||||
return nil, gerror.Newf("ICCID当前订购不属于来源出库批次:%s", iccid)
|
||||
}
|
||||
|
||||
orderCode := strings.TrimSpace(bind.OrderCode)
|
||||
if orderCode != "" {
|
||||
orderCodeSet[orderCode] = struct{}{}
|
||||
orderUnsubCounts[orderCode]++
|
||||
}
|
||||
subscriptionNos = append(subscriptionNos, bind.SubscriptionNo)
|
||||
logRows = append(logRows, bindToHistory(bind, now, in.BatchNo, in.Operator.UserId))
|
||||
}
|
||||
|
||||
if len(logRows) > 0 {
|
||||
if _, err = tx.Ctx(ctx).Model(dao.TSimBindPackageLogs.Table()).
|
||||
Data(logRows).
|
||||
OnConflict(dao.TSimBindPackageLogs.Columns().SubscriptionNo).
|
||||
Save(); err != nil {
|
||||
return nil, gerror.Wrap(err, "写入订购历史失败")
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := s.repo.DeleteBindsBySubscriptionNos(ctx, tx, subscriptionNos)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "删除当前订购失败")
|
||||
}
|
||||
if int(affected) != len(iccids) {
|
||||
return nil, gerror.New("部分当前订购已变化,请刷新后重试")
|
||||
}
|
||||
|
||||
orderCodes := mapKeys(orderCodeSet)
|
||||
if err = s.updateOrdersAfterUnsub(ctx, tx, orderCodes, orderUnsubCounts, in.BatchNo, now, in.Operator.UserId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TerminateCurrentPackageOutput{
|
||||
Iccids: iccids,
|
||||
CardsByIccid: cardsByIccid,
|
||||
BindsByIccid: bindsByIccid,
|
||||
PackageCode: packageCode,
|
||||
OrderCodes: orderCodes,
|
||||
UnsubTime: now,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func bindToHistory(bind *entity.TSimCardBindPkg, actualExpireAt *gtime.Time, unsubBatchNo string, userId int64) do.TSimBindPackageLogs {
|
||||
return do.TSimBindPackageLogs{
|
||||
SubscriptionNo: bind.SubscriptionNo,
|
||||
BatchNo: bind.BatchNo,
|
||||
Iccid: bind.Iccid,
|
||||
Msisdn: bind.Msisdn,
|
||||
DeptId: bind.DeptId,
|
||||
PackageType: bind.PackageType,
|
||||
PackageCode: bind.PackageCode,
|
||||
PreviousPackageCode: bind.PreviousPackageCode,
|
||||
BusinessType: bind.BusinessType,
|
||||
EffectiveStatus: consts.EffectiveStatusTerminated,
|
||||
HandleTime: bind.HandleTime,
|
||||
EffectiveTime: bind.EffectiveTime,
|
||||
ExpectedExpireTime: bind.ExpectedExpireTime,
|
||||
ActualExpireTime: actualExpireAt,
|
||||
DurationMonths: bind.DurationMonths,
|
||||
GiftDurationMonths: bind.GiftDurationMonths,
|
||||
OrderCode: bind.OrderCode,
|
||||
Remark: appendRemark(bind.Remark, fmt.Sprintf("unsubscribeBatchNo=%s", unsubBatchNo)),
|
||||
TenantId: bind.TenantId,
|
||||
CreatedDept: bind.CreatedDept,
|
||||
CreatedBy: bind.CreatedBy,
|
||||
CreatedAt: bind.CreatedAt,
|
||||
UpdatedBy: userId,
|
||||
UpdatedAt: actualExpireAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) updateOrdersAfterUnsub(ctx context.Context, tx gdb.TX, orderCodes []string, unsubCounts map[string]int, batchNo string, unsubTime *gtime.Time, userId int64) error {
|
||||
if len(orderCodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
activeCounts, err := s.repo.ActiveBindCountsByOrderCodes(ctx, tx, orderCodes)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "统计订单剩余订购失败")
|
||||
}
|
||||
for _, orderCode := range orderCodes {
|
||||
returnStatus := consts.OrderReturnStatusSuccess
|
||||
if activeCounts[orderCode] > 0 {
|
||||
returnStatus = consts.OrderReturnStatusPartial
|
||||
}
|
||||
if _, err = tx.Ctx(ctx).Model(dao.TOrder.Table()).
|
||||
Where(dao.TOrder.Columns().OrderSn, orderCode).
|
||||
Data(do.TOrder{
|
||||
ReturnStatus: returnStatus,
|
||||
UpdatedBy: userId,
|
||||
UpdatedAt: unsubTime,
|
||||
}).
|
||||
Update(); err != nil {
|
||||
return gerror.Wrap(err, "更新订单退订状态失败")
|
||||
}
|
||||
if err = updateOrderItemExtra(ctx, tx, orderCode, batchNo, unsubCounts[orderCode], unsubTime, userId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateOrderItemExtra(ctx context.Context, tx gdb.TX, orderCode, batchNo string, unsubQuantity int, unsubTime *gtime.Time, userId int64) error {
|
||||
var item *entity.TOrderItem
|
||||
if err := tx.Ctx(ctx).Model(dao.TOrderItem.Table()).
|
||||
Where(dao.TOrderItem.Columns().OrderSn, orderCode).
|
||||
Limit(1).
|
||||
Scan(&item); err != nil {
|
||||
return gerror.Wrap(err, "查询订单项失败")
|
||||
}
|
||||
if item == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
extra := g.Map{}
|
||||
if strings.TrimSpace(item.ExtraSkuData) != "" {
|
||||
_ = json.Unmarshal([]byte(item.ExtraSkuData), &extra)
|
||||
}
|
||||
extra["lastUnsubscribeBatchNo"] = batchNo
|
||||
extra["lastUnsubscribeTime"] = formatTime(unsubTime)
|
||||
extra["unsubscribedQuantity"] = unsubQuantity
|
||||
|
||||
data, err := json.Marshal(extra)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "生成订单项退订扩展失败")
|
||||
}
|
||||
if _, err = tx.Ctx(ctx).Model(dao.TOrderItem.Table()).
|
||||
Where(dao.TOrderItem.Columns().ItemId, item.ItemId).
|
||||
Data(do.TOrderItem{
|
||||
ExtraSkuData: string(data),
|
||||
UpdatedBy: userId,
|
||||
UpdatedAt: unsubTime,
|
||||
}).
|
||||
Update(); err != nil {
|
||||
return gerror.Wrap(err, "更新订单项退订扩展失败")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendRemark(old, addition string) string {
|
||||
old = strings.TrimSpace(old)
|
||||
addition = strings.TrimSpace(addition)
|
||||
if old == "" {
|
||||
return addition
|
||||
}
|
||||
if addition == "" {
|
||||
return old
|
||||
}
|
||||
return old + "; " + addition
|
||||
}
|
||||
|
||||
func mapKeys(set map[string]struct{}) []string {
|
||||
result := make([]string, 0, len(set))
|
||||
for key := range set {
|
||||
if strings.TrimSpace(key) != "" {
|
||||
result = append(result, key)
|
||||
}
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
func formatTime(t *gtime.Time) string {
|
||||
if t == nil {
|
||||
return ""
|
||||
}
|
||||
return t.Format("Y-m-d H:i:s")
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package cardpackage
|
||||
|
||||
import (
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"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/os/gtime"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
repo *Repository
|
||||
}
|
||||
|
||||
func NewService() *Service {
|
||||
return &Service{repo: NewRepository()}
|
||||
}
|
||||
|
||||
type TerminateCurrentPackageInput struct {
|
||||
BatchNo string
|
||||
SourceBatchNo string
|
||||
Iccids []string
|
||||
Operator batchimport.OperatorContext
|
||||
}
|
||||
|
||||
type TerminateCurrentPackageOutput struct {
|
||||
Iccids []string
|
||||
CardsByIccid map[string]*entity.TSimCardInfo
|
||||
BindsByIccid map[string]*entity.TSimCardBindPkg
|
||||
PackageCode string
|
||||
OrderCodes []string
|
||||
UnsubTime *gtime.Time
|
||||
}
|
||||
|
||||
type SubscribeCard struct {
|
||||
Iccid string
|
||||
Msisdn string
|
||||
Imsi string
|
||||
}
|
||||
|
||||
type CreatePackageSubscriptionInput struct {
|
||||
BatchNo string
|
||||
Cards []SubscribeCard
|
||||
CustomerAgentId int64
|
||||
PackageCode string
|
||||
PackageBeginTime *gtime.Time
|
||||
DurationMonths int
|
||||
GiftDurationMonths int
|
||||
DurationDisplayValue int
|
||||
GiftDurationDisplayValue int
|
||||
DurationDisplayLabel string
|
||||
GiftDurationDisplayLabel string
|
||||
DurationUnit string
|
||||
EndAtPreviousMonthEnd bool
|
||||
Amount string
|
||||
PayType int
|
||||
PayStatus int
|
||||
BalanceAmount float64
|
||||
OfflinePaidAmount float64
|
||||
PayTime *gtime.Time
|
||||
OfflineContractCompleted bool
|
||||
BuyerNote string
|
||||
AdminNote string
|
||||
PreviousPackageCode string
|
||||
BusinessType int
|
||||
OrderRemark string
|
||||
BindRemark string
|
||||
SkipBindInsert bool
|
||||
Operator batchimport.OperatorContext
|
||||
}
|
||||
|
||||
type CreatePackageSubscriptionOutput struct {
|
||||
OrderId int64
|
||||
OrderSn string
|
||||
Customer *entity.SysDept
|
||||
Package *entity.TSimFlowPackageInfo
|
||||
PackageBeginAt *gtime.Time
|
||||
PackageEndAt *gtime.Time
|
||||
SubscribeTime *gtime.Time
|
||||
PackageStatus int
|
||||
EffectiveStatus int
|
||||
Amount float64
|
||||
UnitPrice float64
|
||||
Quantity int
|
||||
BindsByIccid map[string]*entity.TSimCardBindPkg
|
||||
BindRows []do.TSimCardBindPkg
|
||||
}
|
||||
|
||||
type RenewCurrentPackageInput struct {
|
||||
BatchNo string
|
||||
SourceBatchNo string
|
||||
PackageCode string
|
||||
Iccids []string
|
||||
DurationMonths int
|
||||
GiftDurationMonths int
|
||||
DurationDisplayValue int
|
||||
GiftDurationDisplayValue int
|
||||
DurationUnit string
|
||||
PackageBeginTime *gtime.Time
|
||||
PackageEndTime *gtime.Time
|
||||
Amount string
|
||||
PayType int
|
||||
PayStatus int
|
||||
BalanceAmount float64
|
||||
OfflinePaidAmount float64
|
||||
PayTime *gtime.Time
|
||||
OfflineContractCompleted bool
|
||||
BuyerNote string
|
||||
AdminNote string
|
||||
OrderRemark string
|
||||
BindRemark string
|
||||
Operator batchimport.OperatorContext
|
||||
}
|
||||
|
||||
type RenewCurrentPackageOutput struct {
|
||||
Iccids []string
|
||||
OrderId int64
|
||||
OrderSn string
|
||||
Customer *entity.SysDept
|
||||
Package *entity.TSimFlowPackageInfo
|
||||
CardsByIccid map[string]*entity.TSimCardInfo
|
||||
LatestBindsByIccid map[string]*entity.TSimCardBindPkg
|
||||
NewBindsByIccid map[string]*entity.TSimCardBindPkg
|
||||
BindRows []do.TSimCardBindPkg
|
||||
RenewTime *gtime.Time
|
||||
PackageCode string
|
||||
Amount float64
|
||||
UnitPrice float64
|
||||
Quantity int
|
||||
}
|
||||
Reference in New Issue
Block a user