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 已说明。
493 lines
14 KiB
Go
493 lines
14 KiB
Go
package iot
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"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/library/contexts"
|
|
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
|
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
|
|
|
"github.com/gogf/gf/v2/database/gdb"
|
|
"github.com/gogf/gf/v2/encoding/gjson"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
)
|
|
|
|
type stockBusinessFilter struct {
|
|
ChanName string
|
|
CardType string
|
|
UpPkgKeyword string
|
|
AgreementExpiryMonths int
|
|
}
|
|
|
|
type stockBatchBusinessFields struct {
|
|
ChanKey string
|
|
CardType string
|
|
UpPkgCode string
|
|
UpPkgName string
|
|
AgreementExpiryDate string
|
|
}
|
|
|
|
func applyStockBusinessFilters(ctx context.Context, mod *gdb.Model, filter stockBusinessFilter) *gdb.Model {
|
|
if mod == nil {
|
|
return mod
|
|
}
|
|
|
|
stockTable := dao.TSimCardStock.Table()
|
|
stockColumns := dao.TSimCardStock.Columns()
|
|
|
|
if value := strings.TrimSpace(filter.ChanName); value != "" {
|
|
channelColumns := dao.TSimChannels.Columns()
|
|
like := "%" + value + "%"
|
|
mod = mod.Where(
|
|
fmt.Sprintf(
|
|
"EXISTS (SELECT 1 FROM `%s` c WHERE c.`%s` = `%s`.`%s` AND c.`%s` = ? AND (c.`%s` LIKE ? OR c.`%s` LIKE ?))",
|
|
dao.TSimChannels.Table(),
|
|
channelColumns.ChanKey,
|
|
stockTable,
|
|
stockColumns.ChanKey,
|
|
channelColumns.TenantId,
|
|
channelColumns.ChanName,
|
|
channelColumns.ChanKey,
|
|
),
|
|
contexts.GetTenantId(ctx),
|
|
like,
|
|
like,
|
|
)
|
|
}
|
|
|
|
if value := strings.TrimSpace(filter.CardType); value != "" {
|
|
cardTypeExpr := batchInfoTextSQL("bm.`"+dao.BatchMain.Columns().BatchInfo+"`", "cardType")
|
|
mod = mod.Where(stockInboundBatchExistsSQL(cardTypeExpr+" = ?"), consts.BatchTypeInbound, value)
|
|
}
|
|
|
|
if value := strings.TrimSpace(filter.UpPkgKeyword); value != "" {
|
|
upstreamColumns := dao.TUpstreamPackage.Columns()
|
|
upPkgExpr := batchInfoTextSQL("bm.`"+dao.BatchMain.Columns().BatchInfo+"`", "upPkgCode")
|
|
like := "%" + value + "%"
|
|
mod = mod.Where(
|
|
stockInboundBatchJoinExistsSQL(
|
|
fmt.Sprintf("LEFT JOIN `%s` up ON up.`%s` = %s", dao.TUpstreamPackage.Table(), upstreamColumns.UpPkgCode, upPkgExpr),
|
|
fmt.Sprintf("(up.`%s` LIKE ? OR %s LIKE ?)", upstreamColumns.UpPkgName, upPkgExpr),
|
|
),
|
|
consts.BatchTypeInbound,
|
|
like,
|
|
like,
|
|
)
|
|
}
|
|
|
|
if filter.AgreementExpiryMonths > 0 {
|
|
months := filter.AgreementExpiryMonths
|
|
if months != 1 && months != 3 && months != 6 {
|
|
months = 6
|
|
}
|
|
now := time.Now()
|
|
start := now.Format("2006-01-02")
|
|
end := now.AddDate(0, months, 0).Format("2006-01-02")
|
|
agreementExpr := batchInfoTextSQL("bm.`"+dao.BatchMain.Columns().BatchInfo+"`", "agreementExpiryDate")
|
|
mod = mod.Where(
|
|
stockInboundBatchExistsSQL("DATE("+agreementExpr+") BETWEEN ? AND ?"),
|
|
consts.BatchTypeInbound,
|
|
start,
|
|
end,
|
|
)
|
|
}
|
|
|
|
return mod
|
|
}
|
|
|
|
func stockInboundBatchExistsSQL(extra string) string {
|
|
return stockInboundBatchJoinExistsSQL("", extra)
|
|
}
|
|
|
|
func stockInboundBatchJoinExistsSQL(joinSQL, extra string) string {
|
|
stockTable := dao.TSimCardStock.Table()
|
|
stockColumns := dao.TSimCardStock.Columns()
|
|
batchColumns := dao.BatchMain.Columns()
|
|
joinPart := ""
|
|
if strings.TrimSpace(joinSQL) != "" {
|
|
joinPart = " " + joinSQL
|
|
}
|
|
return fmt.Sprintf(
|
|
"EXISTS (SELECT 1 FROM `%s` bm%s WHERE bm.`%s` = `%s`.`%s` AND bm.`%s` = `%s`.`%s` AND bm.`%s` = ? AND %s)",
|
|
dao.BatchMain.Table(),
|
|
joinPart,
|
|
batchColumns.BatchNo,
|
|
stockTable,
|
|
stockColumns.BatchNo,
|
|
batchColumns.TenantId,
|
|
stockTable,
|
|
stockColumns.TenantId,
|
|
batchColumns.BatchType,
|
|
extra,
|
|
)
|
|
}
|
|
|
|
func batchInfoTextSQL(columnExpr, field string) string {
|
|
return fmt.Sprintf(
|
|
"COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(%s, '$.inboundOptions.%s')), ''), JSON_UNQUOTE(JSON_EXTRACT(%s, '$.%s')))",
|
|
columnExpr,
|
|
field,
|
|
columnExpr,
|
|
field,
|
|
)
|
|
}
|
|
|
|
func enrichStockListBusinessFields(ctx context.Context, list []*model.TSimCardStockListModel) error {
|
|
if len(list) == 0 {
|
|
return nil
|
|
}
|
|
|
|
batchNos := make([]string, 0, len(list))
|
|
chanKeys := make([]string, 0, len(list))
|
|
for _, item := range list {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
batchNos = appendUniqueNonBlank(batchNos, item.BatchNo)
|
|
chanKeys = appendUniqueNonBlank(chanKeys, item.ChanKey)
|
|
}
|
|
|
|
batchMap, err := loadStockBatchBusinessFields(ctx, batchNos)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
detailMap, err := loadStockDetailBusinessFields(ctx, batchNos)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, fields := range detailMap {
|
|
chanKeys = appendUniqueNonBlank(chanKeys, fields.ChanKey)
|
|
}
|
|
channelMap, err := loadStockChannelNames(ctx, chanKeys)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, item := range list {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
fields := mergeStockBusinessFields(batchMap[item.BatchNo], detailMap[item.BatchNo])
|
|
item.ChanName = channelMap[firstNonBlankString(item.ChanKey, fields.ChanKey)]
|
|
item.CardType = fields.CardType
|
|
item.UpPkgCode = fields.UpPkgCode
|
|
item.UpPkgName = fields.UpPkgName
|
|
item.AgreementExpiryDate = fields.AgreementExpiryDate
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func enrichOutboundBatchBusinessFields(ctx context.Context, list []*model.TSimCardOutboundInboundBatchListItem) error {
|
|
if len(list) == 0 {
|
|
return nil
|
|
}
|
|
|
|
batchNos := make([]string, 0, len(list))
|
|
chanKeys := make([]string, 0, len(list))
|
|
for _, item := range list {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
batchNos = appendUniqueNonBlank(batchNos, item.BatchNo)
|
|
chanKeys = appendUniqueNonBlank(chanKeys, item.ChanKey)
|
|
}
|
|
|
|
batchMap, err := loadStockBatchBusinessFields(ctx, batchNos)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
detailMap, err := loadStockDetailBusinessFields(ctx, batchNos)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, fields := range detailMap {
|
|
chanKeys = appendUniqueNonBlank(chanKeys, fields.ChanKey)
|
|
}
|
|
channelMap, err := loadStockChannelNames(ctx, chanKeys)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, item := range list {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
fields := mergeStockBusinessFields(batchMap[item.BatchNo], detailMap[item.BatchNo])
|
|
item.ChanName = channelMap[firstNonBlankString(item.ChanKey, fields.ChanKey)]
|
|
item.CardType = fields.CardType
|
|
item.UpPkgCode = fields.UpPkgCode
|
|
item.UpPkgName = fields.UpPkgName
|
|
item.AgreementExpiryDate = fields.AgreementExpiryDate
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func enrichOutboundBatchCardBusinessFields(ctx context.Context, list []*model.TSimCardOutboundInboundBatchCardItem) error {
|
|
if len(list) == 0 {
|
|
return nil
|
|
}
|
|
|
|
batchNos := make([]string, 0, len(list))
|
|
chanKeys := make([]string, 0, len(list))
|
|
for _, item := range list {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
batchNos = appendUniqueNonBlank(batchNos, item.BatchNo)
|
|
chanKeys = appendUniqueNonBlank(chanKeys, item.ChanKey)
|
|
}
|
|
|
|
batchMap, err := loadStockBatchBusinessFields(ctx, batchNos)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
detailMap, err := loadStockDetailBusinessFields(ctx, batchNos)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, fields := range detailMap {
|
|
chanKeys = appendUniqueNonBlank(chanKeys, fields.ChanKey)
|
|
}
|
|
channelMap, err := loadStockChannelNames(ctx, chanKeys)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, item := range list {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
fields := mergeStockBusinessFields(batchMap[item.BatchNo], detailMap[item.BatchNo])
|
|
item.ChanName = channelMap[firstNonBlankString(item.ChanKey, fields.ChanKey)]
|
|
item.CardType = fields.CardType
|
|
item.UpPkgCode = fields.UpPkgCode
|
|
item.UpPkgName = fields.UpPkgName
|
|
item.AgreementExpiryDate = fields.AgreementExpiryDate
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func loadStockBatchBusinessFields(ctx context.Context, batchNos []string) (map[string]stockBatchBusinessFields, error) {
|
|
result := make(map[string]stockBatchBusinessFields)
|
|
batchNos = uniqueNonBlank(batchNos)
|
|
if len(batchNos) == 0 {
|
|
return result, nil
|
|
}
|
|
|
|
var batches []*entity.BatchMain
|
|
columns := dao.BatchMain.Columns()
|
|
if err := dao.BatchMain.Ctx(ctx).
|
|
Fields(columns.BatchNo, columns.BatchInfo).
|
|
Where(columns.TenantId, contexts.GetTenantId(ctx)).
|
|
Where(columns.BatchType, consts.BatchTypeInbound).
|
|
WhereIn(columns.BatchNo, batchNos).
|
|
Scan(&batches); err != nil {
|
|
return nil, gerror.Wrap(err, "补充入库批次业务字段失败")
|
|
}
|
|
|
|
upPkgCodes := make([]string, 0, len(batches))
|
|
for _, batch := range batches {
|
|
if batch == nil {
|
|
continue
|
|
}
|
|
fields := stockBatchBusinessFields{
|
|
ChanKey: batchInfoString(batch.BatchInfo, "chanKey"),
|
|
CardType: batchInfoString(batch.BatchInfo, "cardType"),
|
|
UpPkgCode: batchInfoString(batch.BatchInfo, "upPkgCode"),
|
|
AgreementExpiryDate: batchInfoString(batch.BatchInfo, "agreementExpiryDate"),
|
|
}
|
|
result[batch.BatchNo] = fields
|
|
upPkgCodes = appendUniqueNonBlank(upPkgCodes, fields.UpPkgCode)
|
|
}
|
|
|
|
upPkgNames, err := loadStockUpstreamPackageNames(ctx, upPkgCodes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for batchNo, fields := range result {
|
|
fields.UpPkgName = upPkgNames[fields.UpPkgCode]
|
|
if fields.UpPkgName == "" {
|
|
fields.UpPkgName = fields.UpPkgCode
|
|
}
|
|
result[batchNo] = fields
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func loadStockDetailBusinessFields(ctx context.Context, batchNos []string) (map[string]stockBatchBusinessFields, error) {
|
|
result := make(map[string]stockBatchBusinessFields)
|
|
batchNos = uniqueNonBlank(batchNos)
|
|
if len(batchNos) == 0 {
|
|
return result, nil
|
|
}
|
|
|
|
var details []*entity.BatchDetail
|
|
columns := dao.BatchDetail.Columns()
|
|
if err := dao.BatchDetail.Ctx(ctx).
|
|
Fields(columns.BatchNo, columns.Snapshot).
|
|
Where(columns.TenantId, contexts.GetTenantId(ctx)).
|
|
Where(columns.BatchType, consts.BatchTypeInbound).
|
|
Where(columns.DetailStatus, consts.BatchDetailStatusSuccess).
|
|
WhereIn(columns.BatchNo, batchNos).
|
|
OrderAsc(columns.RowNo).
|
|
Scan(&details); err != nil {
|
|
return nil, gerror.Wrap(err, "load inbound batch detail business fields failed")
|
|
}
|
|
|
|
upPkgCodes := make([]string, 0, len(details))
|
|
for _, detail := range details {
|
|
if detail == nil || detail.Snapshot == nil {
|
|
continue
|
|
}
|
|
current := result[detail.BatchNo]
|
|
fields := stockSnapshotBusinessFields(detail.Snapshot)
|
|
current = mergeStockBusinessFields(current, fields)
|
|
result[detail.BatchNo] = current
|
|
upPkgCodes = appendUniqueNonBlank(upPkgCodes, current.UpPkgCode)
|
|
}
|
|
|
|
upPkgNames, err := loadStockUpstreamPackageNames(ctx, upPkgCodes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for batchNo, fields := range result {
|
|
fields.UpPkgName = upPkgNames[fields.UpPkgCode]
|
|
if fields.UpPkgName == "" {
|
|
fields.UpPkgName = fields.UpPkgCode
|
|
}
|
|
result[batchNo] = fields
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func stockSnapshotBusinessFields(snapshot *gjson.Json) stockBatchBusinessFields {
|
|
if snapshot == nil {
|
|
return stockBatchBusinessFields{}
|
|
}
|
|
return stockBatchBusinessFields{
|
|
ChanKey: snapshotString(snapshot, "chanKey", "rawFields.chanKey"),
|
|
CardType: snapshotString(snapshot, "cardType", "rawFields.cardType"),
|
|
UpPkgCode: snapshotString(snapshot, "upPkgCode", "rawFields.upPkgCode"),
|
|
AgreementExpiryDate: snapshotString(snapshot, "agreementExpiryDate", "rawFields.agreementExpiryDate"),
|
|
}
|
|
}
|
|
|
|
func snapshotString(snapshot *gjson.Json, paths ...string) string {
|
|
for _, path := range paths {
|
|
if value := strings.TrimSpace(snapshot.Get(path).String()); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func mergeStockBusinessFields(primary, fallback stockBatchBusinessFields) stockBatchBusinessFields {
|
|
return stockBatchBusinessFields{
|
|
ChanKey: firstNonBlankString(primary.ChanKey, fallback.ChanKey),
|
|
CardType: firstNonBlankString(primary.CardType, fallback.CardType),
|
|
UpPkgCode: firstNonBlankString(primary.UpPkgCode, fallback.UpPkgCode),
|
|
UpPkgName: firstNonBlankString(primary.UpPkgName, fallback.UpPkgName),
|
|
AgreementExpiryDate: firstNonBlankString(primary.AgreementExpiryDate, fallback.AgreementExpiryDate),
|
|
}
|
|
}
|
|
|
|
func firstNonBlankString(values ...string) string {
|
|
for _, value := range values {
|
|
if trimmed := strings.TrimSpace(value); trimmed != "" {
|
|
return trimmed
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func loadStockChannelNames(ctx context.Context, chanKeys []string) (map[string]string, error) {
|
|
result := make(map[string]string)
|
|
chanKeys = uniqueNonBlank(chanKeys)
|
|
if len(chanKeys) == 0 {
|
|
return result, nil
|
|
}
|
|
|
|
type channelRow struct {
|
|
ChanKey string `orm:"chan_key"`
|
|
ChanName string `orm:"chan_name"`
|
|
}
|
|
var rows []*channelRow
|
|
columns := dao.TSimChannels.Columns()
|
|
if err := dao.TSimChannels.Ctx(ctx).
|
|
Fields(columns.ChanKey, columns.ChanName).
|
|
Where(columns.TenantId, contexts.GetTenantId(ctx)).
|
|
WhereIn(columns.ChanKey, chanKeys).
|
|
Scan(&rows); err != nil {
|
|
return nil, gerror.Wrap(err, "补充通道名称失败")
|
|
}
|
|
for _, row := range rows {
|
|
if row != nil {
|
|
result[row.ChanKey] = row.ChanName
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func loadStockUpstreamPackageNames(ctx context.Context, upPkgCodes []string) (map[string]string, error) {
|
|
result := make(map[string]string)
|
|
upPkgCodes = uniqueNonBlank(upPkgCodes)
|
|
if len(upPkgCodes) == 0 {
|
|
return result, nil
|
|
}
|
|
|
|
type upstreamPackageRow struct {
|
|
UpPkgCode string `orm:"up_pkg_code"`
|
|
UpPkgName string `orm:"up_pkg_name"`
|
|
}
|
|
var rows []*upstreamPackageRow
|
|
columns := dao.TUpstreamPackage.Columns()
|
|
if err := dao.TUpstreamPackage.Ctx(ctx).
|
|
Fields(columns.UpPkgCode, columns.UpPkgName).
|
|
WhereIn(columns.UpPkgCode, upPkgCodes).
|
|
Scan(&rows); err != nil {
|
|
return nil, gerror.Wrap(err, "补充上游套餐名称失败")
|
|
}
|
|
for _, row := range rows {
|
|
if row != nil {
|
|
result[row.UpPkgCode] = row.UpPkgName
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func batchInfoString(info *gjson.Json, field string) string {
|
|
if info == nil {
|
|
return ""
|
|
}
|
|
if value := strings.TrimSpace(info.Get("inboundOptions." + field).String()); value != "" {
|
|
return value
|
|
}
|
|
return strings.TrimSpace(info.Get(field).String())
|
|
}
|
|
|
|
func appendUniqueNonBlank(values []string, value string) []string {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return values
|
|
}
|
|
for _, item := range values {
|
|
if item == value {
|
|
return values
|
|
}
|
|
}
|
|
return append(values, value)
|
|
}
|
|
|
|
func uniqueNonBlank(values []string) []string {
|
|
result := make([]string, 0, len(values))
|
|
for _, value := range values {
|
|
result = appendUniqueNonBlank(result, value)
|
|
}
|
|
return result
|
|
}
|