Files
iotlcm/server/internal/logic/iot/stock_business_fields.go
T
MiFaZhan 59eb34976a chore: 初始开源发布
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 已说明。
2026-10-23 14:48:30 +08:00

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
}