Files
iotlcm/admin-svr/server/internal/logic/iot/t_sim_card_outbound.go
T

455 lines
14 KiB
Go

package iot
import (
"context"
"database/sql"
"errors"
"strconv"
"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/library/batchimport"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/iot/simcardbatch/outbound"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/iot_excelize"
"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/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
"github.com/xuri/excelize/v2"
)
type sIotTSimCardOutbound struct{}
const outboundFinanceAccountStatusNormal = 0
func NewIotTSimCardOutbound() *sIotTSimCardOutbound {
return &sIotTSimCardOutbound{}
}
func init() {
service.RegisterIotTSimCardOutbound(NewIotTSimCardOutbound())
}
func (s *sIotTSimCardOutbound) DownloadExcelTemplate(ctx context.Context) error {
f := excelize.NewFile()
defer func() { _ = f.Close() }()
headers := outbound.NewProcessor(outbound.PreviewOptions{}).Config().TemplateHeaders
if err := f.SetSheetRow("Sheet1", "A1", &headers); err != nil {
return err
}
if err := iot_excelize.ApplyHeaderColumnsTextStyle(f); err != nil {
return err
}
return iot_excelize.WriteHTTP(ctx, f, "sim_card_outbound_template")
}
func (s *sIotTSimCardOutbound) CustomerBalance(ctx context.Context, in *model.TSimCardOutboundCustomerBalanceParam) (*model.TSimCardOutboundCustomerBalanceRes, error) {
if in == nil || in.CustomerAgentId <= 0 {
return nil, gerror.New("客户不能为空")
}
operatorDeptId := contexts.GetDeptId(ctx)
tenantId := contexts.GetTenantId(ctx)
if operatorDeptId <= 0 || tenantId == "" {
return nil, gerror.New("当前用户未绑定组织或租户")
}
customer := &entity.SysDept{}
if err := dao.SysDept.Ctx(ctx).
Fields(dao.SysDept.Columns().DeptId, dao.SysDept.Columns().DeptName, dao.SysDept.Columns().ParentId).
Where(dao.SysDept.Columns().DeptId, in.CustomerAgentId).
Where(dao.SysDept.Columns().TenantId, tenantId).
Scan(customer); err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, gerror.Wrap(err, "查询客户信息失败")
}
if customer.DeptId == 0 || (customer.DeptId != operatorDeptId && customer.ParentId != operatorDeptId) {
return nil, gerror.New("客户不存在或不在当前操作范围内")
}
res := &model.TSimCardOutboundCustomerBalanceRes{
CustomerAgentId: customer.DeptId,
CustomerName: customer.DeptName,
AccountExists: false,
StatusText: "暂无余额账户",
}
account := &entity.TAgentBalance{}
if err := dao.TAgentBalance.Ctx(ctx).
Where(dao.TAgentBalance.Columns().DeptId, customer.DeptId).
Where(dao.TAgentBalance.Columns().TenantId, tenantId).
Scan(account); err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, gerror.Wrap(err, "查询账户余额失败")
}
if account.Id == 0 {
return res, nil
}
res.AccountExists = true
res.Balance = account.Balance
res.Status = account.Status
if account.Status == outboundFinanceAccountStatusNormal {
res.StatusText = "正常"
} else {
res.StatusText = "禁用"
}
return res, nil
}
func (s *sIotTSimCardOutbound) validateBalanceBeforeSubmit(ctx context.Context, in *model.TSimCardOutboundSubmitParam) error {
if in == nil || in.OfflineContractCompleted {
return nil
}
amountText := strings.TrimSpace(in.Amount)
if amountText == "" {
return gerror.New("订单金额不能为空")
}
amount, err := strconv.ParseFloat(amountText, 64)
if err != nil || amount <= 0 {
return gerror.New("订单金额必须大于0")
}
balance, err := s.CustomerBalance(ctx, &model.TSimCardOutboundCustomerBalanceParam{
CustomerAgentId: in.CustomerAgentId,
})
if err != nil {
return err
}
if balance == nil || !balance.AccountExists {
return gerror.New("客户暂无余额账户,请先充值")
}
if balance.Status != outboundFinanceAccountStatusNormal {
return gerror.New("客户余额账户状态异常,不能出库")
}
if balance.Balance+0.0001 < amount {
return gerror.Newf("客户余额不足,需支付%.2f元,当前余额%.2f元", amount, balance.Balance)
}
return nil
}
func (s *sIotTSimCardOutbound) SubmitExcel(ctx context.Context, in *model.TSimCardOutboundExcelSubmitParam) (*model.TSimCardOutboundExcelSubmitRes, error) {
if in == nil {
in = &model.TSimCardOutboundExcelSubmitParam{}
}
file, err := s.getUploadFile(ctx)
if err != nil {
return nil, err
}
result, err := outbound.SubmitExcelAsync(ctx, file, in)
if err != nil {
return nil, err
}
return (*model.TSimCardOutboundExcelSubmitRes)(outboundSubmitResult(result, consts.BatchTypeOutbound)), nil
}
func (s *sIotTSimCardOutbound) SubmitRange(ctx context.Context, in *model.TSimCardOutboundRangeSubmitParam) (*model.TSimCardOutboundRangeSubmitRes, error) {
if in == nil {
in = &model.TSimCardOutboundRangeSubmitParam{}
}
normalizeOutboundSubmitParam(&in.TSimCardOutboundSubmitParam)
if err := s.validateBalanceBeforeSubmit(ctx, &in.TSimCardOutboundSubmitParam); err != nil {
return nil, err
}
rows, err := outbound.BuildRangeRows(ctx, in.RangeCodeType, in.StartIccid, in.EndIccid, in.StartMsisdn, in.EndMsisdn, in.StartImsi, in.EndImsi)
if err != nil {
return nil, err
}
result, err := outbound.SubmitRowsAsync(ctx, in, rows, consts.BatchSourceTypeRange, "", true)
if err != nil {
return nil, err
}
return (*model.TSimCardOutboundRangeSubmitRes)(outboundSubmitResult(result, consts.BatchTypeOutbound)), nil
}
func (s *sIotTSimCardOutbound) SubmitInput(ctx context.Context, in *model.TSimCardOutboundInputSubmitParam) (*model.TSimCardOutboundInputSubmitRes, error) {
if in == nil {
in = &model.TSimCardOutboundInputSubmitParam{}
}
normalizeOutboundSubmitParam(&in.TSimCardOutboundSubmitParam)
if err := s.validateBalanceBeforeSubmit(ctx, &in.TSimCardOutboundSubmitParam); err != nil {
return nil, err
}
rows, err := outbound.ParseInputRows(ctx, in.Input)
if err != nil {
return nil, err
}
result, err := outbound.SubmitRowsAsync(ctx, in, rows, consts.BatchSourceTypeBatchInput, "", false)
if err != nil {
return nil, err
}
return (*model.TSimCardOutboundInputSubmitRes)(outboundSubmitResult(result, consts.BatchTypeOutbound)), nil
}
func (s *sIotTSimCardOutbound) ListInboundBatches(ctx context.Context, in *model.TSimCardOutboundInboundBatchListParam) (*model.TSimCardOutboundInboundBatchListRes, error) {
if in == nil {
in = &model.TSimCardOutboundInboundBatchListParam{}
}
normalizePage(&in.Page, &in.PageSize)
base := outboundStockBaseModel(ctx)
columns := dao.TSimCardStock.Columns()
if batchNo := strings.TrimSpace(in.BatchNo); batchNo != "" {
base = base.WhereLike(columns.BatchNo, "%"+batchNo+"%")
}
base = applyStockBusinessFilters(ctx, base, stockBusinessFilter{
ChanName: in.ChanName,
CardType: in.CardType,
UpPkgKeyword: in.UpPkgKeyword,
AgreementExpiryMonths: in.AgreementExpiryMonths,
})
totalValue, err := base.Clone().Fields("COUNT(DISTINCT " + columns.BatchNo + ")").Value()
if err != nil {
return nil, gerror.Wrap(err, "查询可出库批次列表失败")
}
total := gconv.Int(totalValue)
var items []*model.TSimCardOutboundInboundBatchListItem
err = base.Fields(
columns.BatchNo,
"MIN("+columns.ChanKey+") AS chan_key",
"COUNT(1) AS stock_count",
"MIN("+columns.CreatedAt+") AS first_created_at",
"MAX("+columns.CreatedAt+") AS last_created_at",
).
Group(columns.BatchNo).
OrderDesc("last_created_at").
Page(in.Page, in.PageSize).
Scan(&items)
if err != nil {
return nil, gerror.Wrap(err, "查询可出库批次列表失败")
}
if err = enrichOutboundBatchBusinessFields(ctx, items); err != nil {
return nil, err
}
return &model.TSimCardOutboundInboundBatchListRes{
Items: items,
Total: total,
}, nil
}
func (s *sIotTSimCardOutbound) ListInboundBatchCards(ctx context.Context, in *model.TSimCardOutboundInboundBatchCardsParam) (*model.TSimCardOutboundInboundBatchCardsRes, error) {
if in == nil {
in = &model.TSimCardOutboundInboundBatchCardsParam{}
}
normalizePage(&in.Page, &in.PageSize)
batchNo := strings.TrimSpace(in.BatchNo)
if batchNo == "" {
return nil, gerror.New("批次号不能为空")
}
mod := outboundStockBaseModel(ctx).Where(dao.TSimCardStock.Columns().BatchNo, batchNo)
mod = filterOutboundStockCards(mod, in.Iccid, in.Msisdn, in.Imsi)
var items []*model.TSimCardOutboundInboundBatchCardItem
total, err := mod.Clone().Count()
if err != nil {
return nil, gerror.Wrap(err, "查询可出库批次卡列表失败")
}
err = mod.Fields(outboundBatchCardSelectFields()).
OrderAsc(dao.TSimCardStock.Columns().Id).
Page(in.Page, in.PageSize).
Scan(&items)
if err != nil {
return nil, gerror.Wrap(err, "查询可出库批次卡列表失败")
}
if err = enrichOutboundBatchCardBusinessFields(ctx, items); err != nil {
return nil, err
}
return &model.TSimCardOutboundInboundBatchCardsRes{
Items: items,
Total: total,
}, nil
}
func (s *sIotTSimCardOutbound) SubmitBatch(ctx context.Context, in *model.TSimCardOutboundBatchSubmitParam) (*model.TSimCardOutboundBatchSubmitRes, error) {
if in == nil {
in = &model.TSimCardOutboundBatchSubmitParam{}
}
normalizeOutboundSubmitParam(&in.TSimCardOutboundSubmitParam)
if err := s.validateBalanceBeforeSubmit(ctx, &in.TSimCardOutboundSubmitParam); err != nil {
return nil, err
}
batchNo := strings.TrimSpace(in.BatchNo)
if batchNo == "" {
return nil, gerror.New("批次号不能为空")
}
rows, err := s.buildBatchOutboundRows(ctx, batchNo)
if err != nil {
return nil, err
}
result, err := outbound.SubmitRowsAsync(ctx, in, rows, consts.BatchSourceTypeBatchSelect, batchNo, false)
if err != nil {
return nil, err
}
return (*model.TSimCardOutboundBatchSubmitRes)(outboundSubmitResult(result, consts.BatchTypeOutbound)), nil
}
func (s *sIotTSimCardOutbound) getUploadFile(ctx context.Context) (*ghttp.UploadFile, error) {
r := g.RequestFromCtx(ctx)
if r == nil {
return nil, gerror.New("当前上下文不是HTTP请求")
}
file := r.GetUploadFile("file")
if file == nil {
return nil, gerror.New("请选择上传文件")
}
return file, nil
}
func (s *sIotTSimCardOutbound) buildBatchOutboundRows(ctx context.Context, batchNo string) ([]batchimport.Row, error) {
mod := outboundStockBaseModel(ctx).Where(dao.TSimCardStock.Columns().BatchNo, batchNo)
var cards []*model.TSimCardOutboundInboundBatchCardItem
err := mod.Fields(outboundBatchCardSelectFields()).
OrderAsc(dao.TSimCardStock.Columns().Id).
Scan(&cards)
if err != nil {
return nil, gerror.Wrap(err, "查询可出库批次卡列表失败")
}
if len(cards) == 0 {
return nil, gerror.New("当前批次没有可出库库存卡")
}
rows := make([]batchimport.Row, 0, len(cards))
for index, card := range cards {
rows = append(rows, &outbound.Row{
RowNo: index + 1,
Iccid: strings.TrimSpace(card.Iccid),
Msisdn: strings.TrimSpace(card.Msisdn),
Imsi: strings.TrimSpace(card.Imsi),
SourceBatchNo: batchNo,
})
}
return rows, nil
}
func outboundStockBaseModel(ctx context.Context) *gdb.Model {
columns := dao.TSimCardStock.Columns()
return dao.TSimCardStock.Ctx(ctx).
Where(columns.TenantId, contexts.GetTenantId(ctx)).
Where(columns.CreatedDept, contexts.GetDeptId(ctx)).
Where(columns.BatchNo+" <> ?", "")
}
func filterOutboundStockCards(mod *gdb.Model, iccid, msisdn, imsi string) *gdb.Model {
columns := dao.TSimCardStock.Columns()
if value := strings.TrimSpace(iccid); value != "" {
mod = mod.WhereLike(columns.Iccid, "%"+value+"%")
}
if value := strings.TrimSpace(msisdn); value != "" {
mod = mod.WhereLike(columns.Msisdn, "%"+value+"%")
}
if value := strings.TrimSpace(imsi); value != "" {
mod = mod.WhereLike(columns.Imsi, "%"+value+"%")
}
return mod
}
func outboundBatchCardSelectFields() []string {
columns := dao.TSimCardStock.Columns()
return []string{
columns.Iccid,
columns.Msisdn,
columns.Imsi,
columns.Imei,
columns.ChanKey,
columns.BatchNo,
columns.CreatedAt,
}
}
func outboundSubmitResult(result *batchimport.Result, batchType int) *model.TSimCardOutboundSubmitRes {
res := &model.TSimCardOutboundSubmitRes{
BatchType: batchType,
BatchAsyncStatus: consts.BatchAsyncStatusPending,
Status: "pending",
}
if result != nil {
res.BatchNo = result.BatchNo
res.Total = result.TotalRows
res.Quantity = result.TotalRows
res.ValidCount = result.TotalRows
res.FailCount = 0
res.ResultDownloadId = result.ResultDownloadId
res.ResultFileOssId = result.ResultFileOssId
}
return res
}
func normalizeOutboundSubmitParam(in *model.TSimCardOutboundSubmitParam) {
if in == nil {
return
}
if in.DurationMonths <= 0 && in.DurationYears > 0 {
in.DurationMonths = in.DurationYears*12 + in.DurationMonthPart
}
if in.DurationMonths <= 0 && in.DurationMonthPart > 0 {
in.DurationMonths = in.DurationMonthPart
}
if in.DurationYears <= 0 && in.DurationMonthPart <= 0 && in.DurationMonths > 0 {
in.DurationYears = in.DurationMonths / 12
in.DurationMonthPart = in.DurationMonths % 12
}
if in.GiftDurationMonths <= 0 && in.GiftDurationYears > 0 {
in.GiftDurationMonths = in.GiftDurationYears*12 + in.GiftDurationMonthPart
}
if in.GiftDurationMonths <= 0 && in.GiftDurationMonthPart > 0 {
in.GiftDurationMonths = in.GiftDurationMonthPart
}
if in.GiftDurationYears <= 0 && in.GiftDurationMonthPart <= 0 && in.GiftDurationMonths > 0 {
in.GiftDurationYears = in.GiftDurationMonths / 12
in.GiftDurationMonthPart = in.GiftDurationMonths % 12
}
disableOutboundPeriodOptions(in)
if strings.TrimSpace(in.Remark) != "" {
in.BuyerNote = strings.TrimSpace(in.Remark)
in.AdminNote = strings.TrimSpace(in.Remark)
}
}
func disableOutboundPeriodOptions(in *model.TSimCardOutboundSubmitParam) {
if in == nil {
return
}
// 1 表示否。出库不再暴露测试期、沉默期配置,统一按无测试期/无沉默期保存。
in.HasTestPeriod = 1
in.TestPeriodDays = 0
in.TestPeriodLatestExpiryDate = nil
in.HasSilentPeriod = 1
in.SilentPeriodDays = 0
in.SilentPeriodLatestExpiryDate = nil
}
func normalizePage(page *int, pageSize *int) {
if page != nil && *page <= 0 {
*page = 1
}
if pageSize != nil && *pageSize <= 0 {
*pageSize = 20
}
if pageSize != nil && *pageSize > 2000 {
*pageSize = 2000
}
}