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 }