59eb34976a
iotlcm-admin-svr:管理后台服务。 - Go module:iotlcm → git.5iot.com/iotlcm-public/iotlcm-admin-svr(729 文件) - 凭据与地址脱敏:JWT signingKey(含代码里的 MustGet 默认值)、 MySQL DSN、Redis 口令、告警邮件发件人邮箱全部改为占位; 私网 IP → 文档地址段;库名 xiujie_admin → iotlcm - 真实手机号/邮箱 → 合法 11 位编造号 / example.com - 移除内部资产:CI 触发器、过时 DDL(sql/db_iotlcm.sql)、 私有依赖 go get 行、明文 registry 登录口令 - 启动 banner 统一 IOTLCM 方块字 + 社区惯例署名 - Dockerfile 改用公共基础镜像 - 拼错的 RADEME.md → README.md,README 重写 - 需要真实数据库的用例改为可用 IOTLCM_TEST_WITH_DB=1 显式启用 - 补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes 数据库 schema 不随本仓发布(生产 DDL 已过时),README 已说明。
245 lines
7.8 KiB
Go
245 lines
7.8 KiB
Go
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")
|
|
}
|