Files
iotlcm/server/internal/logic/app/app_card_mapper.go
T
MiFaZhan 2ed5dfa6a1 chore: 初始开源发布
iotlcm-app:小程序端 + 用户端服务。

- app/:uni-app 小程序前端(基于 unibest,MIT)
  - 微信 AppID → YOUR_WX_APPID,uni-app AppID → __UNI__YOURAPPID
  - 内网后端地址 → admin.iotlcm.example.com
  - 应用标题与 unibest 模板 README 重写(保留上游致谢与 MIT 声明)
- server/:GoFrame 用户端服务(基于 XiuAdmin,Apache-2.0)
  - module xiuadmin → git.5iot.com/iotlcm-public/iotlcm-app(360 文件)
  - 支付回调地址 → app.iotlcm.example.com,商户名与应用名泛化
  - 真实手机号 → 合法 11 位编造号(保留号段前 3 位 + 后 4 位)
  - 依赖号码尾部的断言同步更新
  - 补 server/LICENSE(Apache-2.0)
- 移除内部资产:CI 触发器
- 补根 README,LF 行尾 + .gitattributes

已知:TestSysDept_RefreshDeptAncestors 在源仓同一提交上同样失败,属既有缺陷。
2026-10-26 14:01:25 +08:00

539 lines
15 KiB
Go

package app
import (
"context"
"fmt"
"strings"
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
func (s *sAppCard) cardRowToModel(ctx context.Context, row *appCardQueryRow, user *entity.SysUser, ownerType string, canBusiness bool) *model.AppCardModel {
if row == nil {
return nil
}
canBusiness = canBusiness && s.canOperateCard(ctx, user, row)
remain := row.PackageTotalFlowMb - row.UsedFlowMb
if remain < 0 {
remain = 0
}
usedPercent := float64(0)
if row.PackageTotalFlowMb > 0 {
usedPercent = row.UsedFlowMb / row.PackageTotalFlowMb * 100
}
isBound := false
if user != nil && s.isTerminalUser(ctx, user) && ownerType == appCardOwnerTerminal {
isBound = true
} else if user != nil {
isBound = s.isCardBoundToUser(ctx, row.Iccid, user)
}
currentPackageCode := appCardCurrentPackageCode(row)
packageName := appCardPackageName(row)
packageType := appCardPackageType(row)
categoryCode := appCardCategoryCode(row)
categoryName := appCardCategoryName(row)
abnormalReasons := appCardAbnormalReasons(row.BossCardStatus, row.PackageStatus)
deptName := ""
deptKey := ""
deptId := int64(0)
tenantId := ""
chanName := ""
chanAlias := ""
if ownerType == appCardOwnerAgent {
actualDeptId := appCardOwnerDeptId(row)
if user != nil && !s.isTerminalUser(ctx, user) && user.DeptId > 0 && actualDeptId > 0 {
if displayDept, err := appEnterpriseDisplayDept(ctx, row.TenantId, user.DeptId, actualDeptId); err == nil && displayDept != nil && displayDept.DeptId > 0 {
deptId = displayDept.DeptId
deptName = strings.TrimSpace(displayDept.DeptName)
deptKey = strings.TrimSpace(displayDept.DeptKey)
}
} else {
deptId = actualDeptId
deptName = appCardOwnerDeptName(row)
deptKey = appCardOwnerDeptKey(row)
}
tenantId = row.TenantId
chanName = row.ChanName
chanAlias = row.ChanAlias
}
return &model.AppCardModel{
Id: row.Id,
Iccid: row.Iccid,
Msisdn: row.Msisdn,
Imsi: row.Imsi,
Imei: row.Imei,
RegionCode: row.RegionCode,
DeptId: deptId,
DeptName: deptName,
DeptKey: deptKey,
CurrentPackageCode: currentPackageCode,
PackageName: packageName,
PackageType: packageType,
CategoryCode: categoryCode,
CategoryName: categoryName,
PackageStatus: row.PackageStatus,
AccessType: row.AccessType,
BossCardStatus: row.BossCardStatus,
AbnormalReason: strings.Join(abnormalReasons, "、"),
AbnormalReasons: abnormalReasons,
CardType: row.CardType,
NetModel: row.NetModel,
PackageTotalFlowMb: row.PackageTotalFlowMb,
UsedFlowMb: row.UsedFlowMb,
RemainFlowMb: remain,
UsedFlowPercent: usedPercent,
FlowPkgBeginAt: row.FlowPkgBeginAt,
FlowPkgEndAt: row.FlowPkgEndAt,
DataServiceStatus: row.DataServiceStatus,
PackageTotalSms: row.PackageTotalSms,
UsedSms: row.UsedSms,
SmsServiceStatus: row.SmsServiceStatus,
PackageTotalVoice: row.PackageTotalVoice,
UsedVoice: row.UsedVoice,
VoiceServiceStatus: row.VoiceServiceStatus,
RoamServiceStatus: row.RoamServiceStatus,
StatusSyncTime: row.StatusSyncTime,
FlowSyncTime: row.FlowSyncTime,
ChanKey: "",
ChanName: chanName,
ChanAlias: chanAlias,
Remark: row.Remark,
TenantId: tenantId,
TerminalName: row.TerminalName,
IsBound: isBound,
CanBusiness: canBusiness,
CanAllocate: s.canAllocateCard(ctx, user, row, ownerType, canBusiness),
OwnerType: ownerType,
DataSource: "local",
}
}
func (s *sAppCard) canAllocateCard(ctx context.Context, user *entity.SysUser, row *appCardQueryRow, ownerType string, canBusiness bool) bool {
return user != nil &&
row != nil &&
ownerType == appCardOwnerAgent &&
!s.isTerminalUser(ctx, user) &&
canBusiness &&
user.DeptId > 0 &&
row.DeptId == user.DeptId
}
func (s *sAppCard) publicCardRowToModel(ctx context.Context, row *appCardQueryRow, cardValue string) *model.AppCardModel {
card := s.cardRowToModel(ctx, row, nil, appCardOwnerPublic, false)
if card == nil {
return nil
}
card.Id = 0
card.DeptId = 0
card.DeptName = ""
card.DeptKey = ""
card.ChanKey = ""
card.ChanName = ""
card.ChanAlias = ""
card.Remark = ""
card.TenantId = ""
card.TerminalName = ""
card.IsBound = false
card.CanBusiness = false
card.CanAllocate = false
card.OwnerType = appCardOwnerPublic
return card
}
func (s *sAppCard) upstreamPublicCardToModel(info *appSimBasicInfoOutput, cardValue string) *model.AppCardModel {
if info == nil {
return nil
}
cardStatus := info.CardStatus
if cardStatus == 0 {
cardStatus = consts.V3ApiSimBossCardStatusActivated
}
packageCode, packageName := appOneLinkMockPackageFromRemark(info.Remark)
abnormalReasons := appCardAbnormalReasons(cardStatus, 0)
return &model.AppCardModel{
Iccid: appFirstNotEmpty(info.Iccid, strings.TrimSpace(cardValue)),
Msisdn: info.Msisdn,
Imsi: info.Imsi,
Imei: info.Imei,
CurrentPackageCode: packageCode,
PackageName: packageName,
BossCardStatus: cardStatus,
AbnormalReason: strings.Join(abnormalReasons, "、"),
AbnormalReasons: abnormalReasons,
StatusSyncTime: appParseOneLinkTime(info.LastChangeDate),
FlowPkgBeginAt: appParseOneLinkTime(info.ActiveDate),
OwnerType: appCardOwnerPublic,
DataSource: info.Provider,
UpstreamProvider: info.Provider,
IsBound: false,
CanBusiness: false,
CanAllocate: false,
}
}
func (s *sAppCard) packageRowToModel(row *appPackageQueryRow, ownerType string, canBusiness bool) *model.AppPackageModel {
if row == nil {
return nil
}
status, statusType := appPackageStatus(row.EffectiveStatus, row.ExpectedExpireTime)
return &model.AppPackageModel{
Id: row.Id,
SubscriptionNo: row.SubscriptionNo,
PackageCode: row.PackageCode,
PackageName: row.PackageName,
PackageType: row.PackageType,
PackageTypeName: appPackageTypeName(row.PackageType),
CategoryCode: row.CategoryCode,
CategoryName: row.CategoryName,
Iccid: row.Iccid,
Msisdn: row.Msisdn,
CardId: row.CardId,
CardValue: appFirstNotEmpty(row.Msisdn, row.Iccid),
CardRemark: row.CardRemark,
TerminalName: row.TerminalName,
BusinessType: row.BusinessType,
BusinessTypeName: appBusinessTypeName(row.BusinessType),
EffectiveStatus: row.EffectiveStatus,
Status: status,
StatusType: statusType,
FilterValue: row.PackageCode,
EffectiveTime: row.EffectiveTime,
ExpireTime: row.ExpectedExpireTime,
ValidText: appPackageValidText(row.EffectiveTime, row.ExpectedExpireTime),
EffectText: appPackageEffectText(row.EffectiveStatus),
FlowMb: row.FlowMb,
FlowText: appFlowText(row.FlowMb),
OwnerType: ownerType,
CanBusiness: canBusiness,
}
}
func (s *sAppCard) publicPackageRowToModel(card *appCardQueryRow, pkg *appPublicPackageQueryRow) *model.AppPackageModel {
if card == nil || pkg == nil {
return nil
}
effectiveStatus := appEffectiveStatusFromCardPackageStatus(card.PackageStatus)
status, statusType := appPackageStatus(effectiveStatus, card.FlowPkgEndAt)
flowMb := pkg.FlowMb
if flowMb <= 0 {
flowMb = card.PackageTotalFlowMb
}
return &model.AppPackageModel{
Id: pkg.Id,
PackageCode: pkg.PackageCode,
PackageName: pkg.PackageName,
PackageType: pkg.PackageType,
PackageTypeName: appPackageTypeName(pkg.PackageType),
CategoryCode: pkg.CategoryCode,
CategoryName: pkg.CategoryName,
Iccid: card.Iccid,
Msisdn: card.Msisdn,
CardId: card.Id,
CardValue: appFirstNotEmpty(card.Msisdn, card.Iccid),
CardRemark: card.Remark,
TerminalName: card.TerminalName,
BusinessTypeName: "当前套餐",
EffectiveStatus: effectiveStatus,
Status: status,
StatusType: statusType,
FilterValue: pkg.PackageCode,
EffectiveTime: card.FlowPkgBeginAt,
ExpireTime: card.FlowPkgEndAt,
ValidText: appPackageValidText(card.FlowPkgBeginAt, card.FlowPkgEndAt),
EffectText: appPackageEffectText(effectiveStatus),
FlowMb: flowMb,
FlowText: appFlowText(flowMb),
OwnerType: appCardOwnerPublic,
CanBusiness: false,
}
}
func appSanitizePublicPackageModel(pkg *model.AppPackageModel, cardValue string) *model.AppPackageModel {
if pkg == nil {
return nil
}
pkg.Id = 0
pkg.SubscriptionNo = ""
pkg.CardId = 0
pkg.CardValue = appFirstNotEmpty(pkg.Msisdn, pkg.Iccid, strings.TrimSpace(cardValue))
pkg.CardRemark = ""
pkg.TerminalName = ""
pkg.BusinessType = 0
pkg.BusinessTypeName = "当前套餐"
pkg.OwnerType = appCardOwnerPublic
pkg.CanBusiness = false
return pkg
}
func appPackageStatus(status int, expireAt *gtime.Time) (string, string) {
if expireAt != nil && !expireAt.IsZero() && expireAt.Before(gtime.Now()) {
return "已过期", "danger"
}
if status == 1 && expireAt != nil && !expireAt.IsZero() && expireAt.Before(gtime.Now().AddDate(0, 0, 30)) {
return "即将到期", "warning"
}
switch status {
case 0:
return "待生效", "primary"
case 1:
return "生效中", "success"
case 2:
return "已过期", "danger"
case 3:
return "已终止", "danger"
case 4:
return "已作废", "danger"
default:
return "未知", "primary"
}
}
func appCardAbnormalReasons(bossCardStatus int, packageStatus int) []string {
reasons := make([]string, 0, 2)
if packageStatus == consts.PackageStatusExpired {
reasons = append(reasons, "套餐已过期")
}
switch bossCardStatus {
case 0:
reasons = append(reasons, "卡状态未知")
case consts.V3ApiSimBossCardStatusStopped:
reasons = append(reasons, "卡状态停机")
case 8:
reasons = append(reasons, "卡状态预销户")
case 9:
reasons = append(reasons, "卡状态已销户")
case consts.V3ApiSimBossCardStatusPendingActivate,
consts.V3ApiSimBossCardStatusActivated,
6,
7:
default:
reasons = append(reasons, fmt.Sprintf("未知卡状态:%d", bossCardStatus))
}
return reasons
}
func appEffectiveStatusFromCardPackageStatus(status int) int {
switch status {
case 1:
return 0
case 2:
return 1
case 3:
return 2
default:
return 4
}
}
func appPackageTypeName(packageType int) string {
switch packageType {
case 101:
return "单卡套餐"
case 102:
return "池套餐"
case 103:
return "单卡加油包"
case 104:
return "池卡加油包"
default:
return gconv.String(packageType)
}
}
func appBusinessTypeName(businessType int) string {
switch businessType {
case 0:
return "订购"
case 1:
return "变更"
case 2:
return "续费"
default:
return gconv.String(businessType)
}
}
func appPackageValidText(beginAt, expireAt *gtime.Time) string {
if beginAt == nil || beginAt.IsZero() || expireAt == nil || expireAt.IsZero() {
return ""
}
return beginAt.Format("Y-m-d") + " 至 " + expireAt.Format("Y-m-d")
}
func appPackageEffectText(status int) string {
switch status {
case 0:
return "套餐待生效"
case 1:
return "套餐生效中"
case 2:
return "套餐已过期"
case 3:
return "套餐已终止"
case 4:
return "套餐已作废"
default:
return ""
}
}
func appFlowText(flowMb float64) string {
if flowMb <= 0 {
return ""
}
if flowMb >= 1024 {
return fmt.Sprintf("%.2fGB", flowMb/1024)
}
return fmt.Sprintf("%.0fMB", flowMb)
}
func appFirstNotEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return value
}
}
return ""
}
func appCardOwnerDeptId(row *appCardQueryRow) int64 {
if row == nil {
return 0
}
if row.TerminalDeptId > 0 {
return row.TerminalDeptId
}
return row.DeptId
}
func appCardOwnerDeptName(row *appCardQueryRow) string {
if row == nil {
return ""
}
return strings.TrimSpace(row.OwnerDeptName)
}
func appCardOwnerDeptKey(row *appCardQueryRow) string {
if row == nil {
return ""
}
return strings.TrimSpace(row.OwnerDeptKey)
}
func appCardCurrentPackageCode(row *appCardQueryRow) string {
if row == nil {
return ""
}
if strings.TrimSpace(row.TerminalCurrentPackageCode) != "" {
return strings.TrimSpace(row.TerminalCurrentPackageCode)
}
return strings.TrimSpace(row.CurrentPackageCode)
}
func appCardPackageName(row *appCardQueryRow) string {
if row == nil {
return ""
}
terminalPackageCode := strings.TrimSpace(row.TerminalCurrentPackageCode)
if terminalPackageCode != "" {
if strings.TrimSpace(row.TerminalPackageName) != "" {
return row.TerminalPackageName
}
if terminalPackageCode != strings.TrimSpace(row.CurrentPackageCode) {
return ""
}
}
return row.PackageName
}
func appCardPackageType(row *appCardQueryRow) int {
if row == nil {
return 0
}
terminalPackageCode := strings.TrimSpace(row.TerminalCurrentPackageCode)
if terminalPackageCode != "" {
if row.TerminalPackageType > 0 {
return row.TerminalPackageType
}
if terminalPackageCode != strings.TrimSpace(row.CurrentPackageCode) {
return 0
}
}
return row.PackageType
}
func appCardCategoryCode(row *appCardQueryRow) string {
if row == nil {
return ""
}
terminalPackageCode := strings.TrimSpace(row.TerminalCurrentPackageCode)
if terminalPackageCode != "" {
if strings.TrimSpace(row.TerminalCategoryCode) != "" {
return row.TerminalCategoryCode
}
if terminalPackageCode != strings.TrimSpace(row.CurrentPackageCode) {
return ""
}
}
return row.CategoryCode
}
func appCardCategoryName(row *appCardQueryRow) string {
if row == nil {
return ""
}
terminalPackageCode := strings.TrimSpace(row.TerminalCurrentPackageCode)
if terminalPackageCode != "" {
if strings.TrimSpace(row.TerminalCategoryName) != "" {
return row.TerminalCategoryName
}
if terminalPackageCode != strings.TrimSpace(row.CurrentPackageCode) {
return ""
}
}
return row.CategoryName
}
func appOneLinkMockPackageFromRemark(remark string) (packageCode string, packageName string) {
for _, part := range strings.Split(remark, ";") {
item := strings.TrimSpace(part)
if item == "" {
continue
}
key, value, ok := strings.Cut(item, "=")
if !ok {
continue
}
switch strings.TrimSpace(key) {
case "package":
packageCode = strings.TrimSpace(value)
case "name":
packageName = strings.TrimSpace(value)
}
}
if packageName == "" {
packageName = packageCode
}
return packageCode, packageName
}
func appParseOneLinkTime(value string) *gtime.Time {
value = strings.TrimSpace(value)
if value == "" {
return nil
}
parsed := gtime.NewFromStr(value)
if parsed == nil || parsed.IsZero() {
return nil
}
return parsed
}