2ed5dfa6a1
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 在源仓同一提交上同样失败,属既有缺陷。
1205 lines
38 KiB
Go
1205 lines
38 KiB
Go
package app
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"math"
|
||
"reflect"
|
||
"sort"
|
||
"strings"
|
||
"time"
|
||
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||
|
||
"github.com/gogf/gf/v2/database/gdb"
|
||
"github.com/gogf/gf/v2/errors/gerror"
|
||
"github.com/gogf/gf/v2/os/gtime"
|
||
"github.com/gogf/gf/v2/util/gconv"
|
||
)
|
||
|
||
type appRenewPriceSnapshot struct {
|
||
UnitPrice float64
|
||
PriceSource string
|
||
AgentDefaultPriceId int64
|
||
TargetPriceId int64
|
||
}
|
||
|
||
type appRenewEffectiveBaseCosts struct {
|
||
AgentCost float64
|
||
TerminalCost float64
|
||
}
|
||
|
||
type appRenewPriceRule struct {
|
||
PriceSource string
|
||
AgentDefaultPriceId int64
|
||
TargetPriceId int64
|
||
AgentCostRatio float64
|
||
AgentCostProfit float64
|
||
TerminalCostRatio float64
|
||
TerminalCostProfit float64
|
||
}
|
||
|
||
type appRenewEffectiveSalePrice struct {
|
||
Rule appRenewPriceRule
|
||
AgentShipmentPrice float64
|
||
TerminalSalePrice float64
|
||
}
|
||
|
||
const (
|
||
appRenewDurationUnitYear = "year"
|
||
appRenewDurationUnitYearText = "年"
|
||
)
|
||
|
||
type appRenewDurationSelection struct {
|
||
Years int
|
||
Months int
|
||
}
|
||
|
||
type appRenewOrderParty struct {
|
||
CustomerType int
|
||
CustomerAgentId int64
|
||
EndUserId int64
|
||
AgentId int64
|
||
CreatedDept int64
|
||
}
|
||
|
||
type appSimTerminalRenewLockRow struct {
|
||
Id int64 `orm:"id"`
|
||
Iccid string `orm:"iccid"`
|
||
TerminalId int64 `orm:"terminal_id"`
|
||
DeptId int64 `orm:"dept_id"`
|
||
CurrentPackageCode string `orm:"current_package_code"`
|
||
TenantId string `orm:"tenant_id"`
|
||
}
|
||
|
||
// RenewPrepare 按当前套餐的 renewable_package_codes 读取可续费目标套餐,
|
||
// 再按用户身份为目标套餐生成单年价和续费年限档位。
|
||
func (s *sAppCard) RenewPrepare(ctx context.Context, param *model.AppCardRenewPrepareParam) (res *model.AppCardRenewPrepareModel, err error) {
|
||
if param == nil {
|
||
param = &model.AppCardRenewPrepareParam{}
|
||
}
|
||
user, err := appCurrentUser(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
card, err := s.findVisibleCard(ctx, user, param.CardValue)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if card == nil {
|
||
return nil, gerror.New("卡信息不存在或无权续费")
|
||
}
|
||
if err = s.ensureOperateCard(ctx, user, card, "续费"); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
renewCtx, err := s.resolveRenewPackageContext(ctx, card, param.SubscriptionNo)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
renewableCodes, renewableTargets, selectedTarget, err := s.resolveRenewTargetPackages(ctx, user, renewCtx, param.PackageCode)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
pkg := selectedTarget.Package
|
||
price := selectedTarget.Price
|
||
durations := selectedTarget.Durations
|
||
|
||
ownerType := s.ownerType(ctx, user)
|
||
cardModel := s.cardRowToModel(ctx, card, user, ownerType, true)
|
||
currentPackageModel := s.renewPackageModel(card, renewCtx.Package, ownerType)
|
||
packageModel := s.renewPackageModel(card, pkg, ownerType)
|
||
effectiveRule := appRenewEffectiveRule(renewCtx.expireAt())
|
||
recommendIndex := appRenewRecommendIndex(durations)
|
||
options := appRenewOptionModels(durations, price.UnitPrice, 1, func(years int) *gtime.Time {
|
||
return appRenewExpireAfter(renewCtx.expireAt(), appRenewActualDurationMonths(years))
|
||
})
|
||
|
||
return &model.AppCardRenewPrepareModel{
|
||
Card: cardModel,
|
||
Cards: []*model.AppCardModel{cardModel},
|
||
Quantity: 1,
|
||
CurrentPackage: currentPackageModel,
|
||
Package: packageModel,
|
||
RenewablePackageCodes: renewableCodes,
|
||
RenewablePackages: s.renewTargetPackageOptionModels(card, renewableTargets, ownerType, 1, func(years int) *gtime.Time {
|
||
return appRenewExpireAfter(renewCtx.expireAt(), appRenewActualDurationMonths(years))
|
||
}),
|
||
Options: options,
|
||
PriceSource: price.PriceSource,
|
||
UnitPrice: price.UnitPrice,
|
||
EffectiveRule: effectiveRule,
|
||
RecommendIndex: recommendIndex,
|
||
}, nil
|
||
}
|
||
|
||
// RenewSubmit 使用账户余额支付,并把单卡续费也写入正式续费批次。
|
||
func (s *sAppCard) RenewSubmit(ctx context.Context, param *model.AppCardRenewSubmitParam) (res *model.AppCardRenewSubmitModel, err error) {
|
||
if param == nil {
|
||
param = &model.AppCardRenewSubmitParam{}
|
||
}
|
||
user, err := appCurrentUser(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
card, err := s.findVisibleCard(ctx, user, param.CardValue)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if card == nil {
|
||
return nil, gerror.New("卡信息不存在或无权续费")
|
||
}
|
||
if err = s.ensureOperateCard(ctx, user, card, "续费"); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
renewCtx, err := s.resolveRenewPackageContext(ctx, card, param.SubscriptionNo)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
renewableCodes, renewableTargets, selectedTarget, err := s.resolveRenewTargetPackages(ctx, user, renewCtx, param.PackageCode)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
pkg := selectedTarget.Package
|
||
price := selectedTarget.Price
|
||
durations := selectedTarget.Durations
|
||
duration := appRenewDurationSelectionFromParam(param.DurationYears, param.DurationMonths, durations)
|
||
if duration.Years <= 0 || duration.Months <= 0 {
|
||
return nil, gerror.New("请选择续费年限")
|
||
}
|
||
if !appContainsInt(durations, duration.Years) {
|
||
return nil, gerror.New("续费时长不在目标套餐可续费范围内")
|
||
}
|
||
|
||
totalAmount := appRenewTotalAmount(price.UnitPrice, duration.Years, 1)
|
||
now := gtime.Now()
|
||
party, err := s.renewOrderParty(ctx, user, card)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var orderId int64
|
||
var orderSn string
|
||
var batchNo string
|
||
var serviceBeginAt *gtime.Time
|
||
var serviceEndAt *gtime.Time
|
||
err = dao.TOrder.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||
batch := &appRenewBatchContext{
|
||
Items: []*appRenewPackageContext{renewCtx},
|
||
CurrentPackage: renewCtx.Package,
|
||
Package: pkg,
|
||
Price: price,
|
||
Party: party,
|
||
Durations: durations,
|
||
OwnerType: s.ownerType(ctx, user),
|
||
RenewablePackageCodes: renewableCodes,
|
||
RenewableTargets: renewableTargets,
|
||
}
|
||
exec, err := s.prepareAppRenewExecution(ctx, tx, batch, duration.Months)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
serviceBeginAt, serviceEndAt = exec.MinBeginAt, exec.MaxEndAt
|
||
orderSn = appOrderNo("SO", user.UserId, now)
|
||
batchNo, err = appNextBatchNo(ctx, consts.BatchNoPrefixRenew)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if _, err = s.deductAppBalanceForDept(ctx, tx, user, orderSn, totalAmount, "套餐续费", party.CustomerAgentId, now); err != nil {
|
||
return err
|
||
}
|
||
orderId, err = s.insertAppRenewOrder(ctx, tx, user, party, strings.TrimSpace(param.BuyerNote), orderSn, totalAmount, now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err = s.insertAppRenewSubscriptionBinds(ctx, tx, user, batch, exec, batchNo, orderSn, duration.Months, now); err != nil {
|
||
return err
|
||
}
|
||
buyerNote := strings.TrimSpace(param.BuyerNote)
|
||
if err = s.insertAppRenewBatchOrderItem(ctx, tx, user, batch, orderId, orderSn, batchNo, "", duration.Months, totalAmount, totalAmount, serviceBeginAt, serviceEndAt, exec.CardPeriods, now); err != nil {
|
||
return err
|
||
}
|
||
return s.insertAppRenewSubmitBatch(ctx, tx, user, batch, batchNo, "", orderSn, duration.Months, totalAmount, totalAmount, serviceBeginAt, serviceEndAt, exec.CardPeriods, buyerNote, now)
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
ownerType := s.ownerType(ctx, user)
|
||
cardModel := s.cardRowToModel(ctx, card, user, ownerType, true)
|
||
currentPackageModel := s.renewPackageModel(card, renewCtx.Package, ownerType)
|
||
packageModel := s.renewPackageModel(card, pkg, ownerType)
|
||
option := appRenewOptionModel(duration.Years, price.UnitPrice, totalAmount, serviceEndAt, appRenewRecommendIndex(durations) == appIndexOfInt(durations, duration.Years))
|
||
cardModels := []*model.AppCardModel{cardModel}
|
||
res = &model.AppCardRenewSubmitModel{
|
||
BatchNo: batchNo,
|
||
BatchType: consts.BatchTypeRenew,
|
||
OrderId: orderId,
|
||
OrderSn: orderSn,
|
||
TotalAmount: totalAmount,
|
||
PriceText: option.PriceText,
|
||
Quantity: 1,
|
||
OrderStatus: consts.OrderStatusCompleted,
|
||
PayStatus: consts.PayStatusPaid,
|
||
Status: "success",
|
||
EffectiveRule: appRenewEffectiveRule(renewCtx.expireAt()),
|
||
Card: cardModel,
|
||
Cards: cardModels,
|
||
CurrentPackage: currentPackageModel,
|
||
Package: packageModel,
|
||
Option: option,
|
||
}
|
||
_ = s.publishAppBusinessAsyncEvent(ctx, appBusinessAsyncEvent{
|
||
EventType: appAsyncEventRenewCompleted,
|
||
OrderSn: orderSn,
|
||
BatchNo: batchNo,
|
||
BatchType: consts.BatchTypeRenew,
|
||
TenantId: user.TenantId,
|
||
UserId: user.UserId,
|
||
DeptId: user.DeptId,
|
||
Trigger: "renew_submit",
|
||
})
|
||
return res, nil
|
||
}
|
||
|
||
func (s *sAppCard) renewPackageModel(card *appCardQueryRow, pkg *entity.TSimFlowPackageInfo, ownerType string) *model.AppPackageModel {
|
||
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: appFirstNotEmpty(pkg.CategoryCode, appCardCategoryCode(card)),
|
||
CategoryName: appCardCategoryName(card),
|
||
Iccid: card.Iccid,
|
||
Msisdn: card.Msisdn,
|
||
CardId: card.Id,
|
||
CardValue: appFirstNotEmpty(card.Msisdn, card.Iccid),
|
||
CardRemark: card.Remark,
|
||
TerminalName: card.TerminalName,
|
||
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: ownerType,
|
||
CanBusiness: true,
|
||
}
|
||
}
|
||
|
||
func (s *sAppCard) renewTargetPackageOptionModels(card *appCardQueryRow, targets []*appRenewTargetContext, ownerType string, quantity int, expireAfter func(years int) *gtime.Time) []*model.AppRenewPackageOptionModel {
|
||
result := make([]*model.AppRenewPackageOptionModel, 0, len(targets))
|
||
for _, target := range targets {
|
||
if target == nil || target.Package == nil || target.Price == nil {
|
||
continue
|
||
}
|
||
options := appRenewOptionModels(target.Durations, target.Price.UnitPrice, quantity, expireAfter)
|
||
result = append(result, &model.AppRenewPackageOptionModel{
|
||
Package: s.renewPackageModel(card, target.Package, ownerType),
|
||
PackageCode: strings.TrimSpace(target.Package.PackageCode),
|
||
PackageName: strings.TrimSpace(target.Package.PackageName),
|
||
RenewUnitPrice: target.Price.UnitPrice,
|
||
UnitPrice: target.Price.UnitPrice,
|
||
PriceSource: target.Price.PriceSource,
|
||
PriceText: fmt.Sprintf("¥%.2f/年", target.Price.UnitPrice),
|
||
RenewableDurationOptions: append([]int(nil), target.Durations...),
|
||
Options: options,
|
||
})
|
||
}
|
||
return result
|
||
}
|
||
|
||
func appRenewOptionModels(durations []int, unitPrice float64, quantity int, expireAfter func(years int) *gtime.Time) []*model.AppRenewOptionModel {
|
||
recommendIndex := appRenewRecommendIndex(durations)
|
||
options := make([]*model.AppRenewOptionModel, 0, len(durations))
|
||
for index, years := range durations {
|
||
var expire *gtime.Time
|
||
if expireAfter != nil {
|
||
expire = expireAfter(years)
|
||
}
|
||
totalAmount := appRenewTotalAmount(unitPrice, years, quantity)
|
||
options = append(options, appRenewOptionModel(years, unitPrice, totalAmount, expire, index == recommendIndex))
|
||
}
|
||
return options
|
||
}
|
||
|
||
func appRenewableDurationOptions(pkg *entity.TSimFlowPackageInfo) ([]int, error) {
|
||
if pkg == nil || pkg.RenewableDurationConfig == nil || pkg.RenewableDurationConfig.IsNil() {
|
||
return nil, gerror.New("当前套餐未配置可续费时长")
|
||
}
|
||
options := appNormalizeRenewDurationOptions(appRenewDurationOptionsFromValue(pkg.RenewableDurationConfig.Interface()))
|
||
sort.Ints(options)
|
||
if len(options) == 0 {
|
||
return nil, gerror.New("当前套餐未配置可续费时长")
|
||
}
|
||
return options, nil
|
||
}
|
||
|
||
func appRenewableDurationYearOptions(pkg *entity.TSimFlowPackageInfo) ([]int, error) {
|
||
if pkg == nil || pkg.RenewableDurationConfig == nil || pkg.RenewableDurationConfig.IsNil() {
|
||
return nil, gerror.New("当前套餐未配置可续费年限")
|
||
}
|
||
options := appNormalizeRenewYearOptions(appRenewDurationOptionsFromValue(pkg.RenewableDurationConfig.Interface()))
|
||
sort.Ints(options)
|
||
if len(options) == 0 {
|
||
return nil, gerror.New("当前套餐未配置可续费年限")
|
||
}
|
||
return options, nil
|
||
}
|
||
|
||
func appRenewDurationOptionsFromValue(value any) []int {
|
||
switch typed := value.(type) {
|
||
case nil:
|
||
return nil
|
||
case []any:
|
||
options := make([]int, 0, len(typed))
|
||
for _, item := range typed {
|
||
options = append(options, appRenewDurationOptionsFromValue(item)...)
|
||
}
|
||
return options
|
||
case map[string]any:
|
||
return appRenewDurationOptionsFromMap(typed)
|
||
case string:
|
||
return appRenewDurationOptionsFromString(typed)
|
||
default:
|
||
if options := appRenewDurationOptionsFromReflectValue(value); len(options) > 0 {
|
||
return options
|
||
}
|
||
if month := gconv.Int(value); month > 0 {
|
||
return []int{month}
|
||
}
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func appRenewDurationOptionsFromReflectValue(value any) []int {
|
||
rv := reflect.ValueOf(value)
|
||
if !rv.IsValid() {
|
||
return nil
|
||
}
|
||
switch rv.Kind() {
|
||
case reflect.Slice, reflect.Array:
|
||
options := make([]int, 0, rv.Len())
|
||
for i := 0; i < rv.Len(); i++ {
|
||
options = append(options, appRenewDurationOptionsFromValue(rv.Index(i).Interface())...)
|
||
}
|
||
return options
|
||
case reflect.Map:
|
||
values := make(map[string]any, rv.Len())
|
||
iter := rv.MapRange()
|
||
for iter.Next() {
|
||
values[gconv.String(iter.Key().Interface())] = iter.Value().Interface()
|
||
}
|
||
return appRenewDurationOptionsFromMap(values)
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func appRenewDurationOptionsFromMap(value map[string]any) []int {
|
||
if len(value) == 0 {
|
||
return nil
|
||
}
|
||
keys := []string{
|
||
"durationMonths",
|
||
"durationMonth",
|
||
"duration",
|
||
"durations",
|
||
"month",
|
||
"months",
|
||
"value",
|
||
"values",
|
||
"time",
|
||
"times",
|
||
"option",
|
||
"options",
|
||
"item",
|
||
"items",
|
||
"data",
|
||
"list",
|
||
"renewableDurationOptions",
|
||
"renewableDurationConfig",
|
||
}
|
||
options := make([]int, 0, len(value))
|
||
for _, key := range keys {
|
||
if item, ok := value[key]; ok {
|
||
options = append(options, appRenewDurationOptionsFromValue(item)...)
|
||
}
|
||
}
|
||
if len(options) > 0 {
|
||
return options
|
||
}
|
||
for key := range value {
|
||
if month := appRenewDurationMonthFromToken(key); month > 0 {
|
||
options = append(options, month)
|
||
}
|
||
}
|
||
return options
|
||
}
|
||
|
||
func appRenewDurationOptionsFromString(value string) []int {
|
||
value = strings.TrimSpace(value)
|
||
if value == "" {
|
||
return nil
|
||
}
|
||
if strings.HasPrefix(value, "[") || strings.HasPrefix(value, "{") {
|
||
var decoded any
|
||
if err := json.Unmarshal([]byte(value), &decoded); err == nil {
|
||
return appRenewDurationOptionsFromValue(decoded)
|
||
}
|
||
}
|
||
parts := strings.FieldsFunc(value, func(r rune) bool {
|
||
switch r {
|
||
case ',', ',', ';', ';', '|', '/', '、', '\n', '\r', '\t', ' ':
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
})
|
||
if len(parts) == 0 {
|
||
parts = []string{value}
|
||
}
|
||
options := make([]int, 0, len(parts))
|
||
for _, part := range parts {
|
||
if month := appRenewDurationMonthFromToken(part); month > 0 {
|
||
options = append(options, month)
|
||
}
|
||
}
|
||
return options
|
||
}
|
||
|
||
func appRenewDurationMonthFromToken(value string) int {
|
||
value = strings.TrimSpace(value)
|
||
if value == "" {
|
||
return 0
|
||
}
|
||
if month := gconv.Int(value); month > 0 {
|
||
return month
|
||
}
|
||
var digits strings.Builder
|
||
for _, r := range value {
|
||
if r >= '0' && r <= '9' {
|
||
digits.WriteRune(r)
|
||
continue
|
||
}
|
||
if digits.Len() > 0 {
|
||
break
|
||
}
|
||
}
|
||
month := gconv.Int(digits.String())
|
||
if month <= 0 {
|
||
return 0
|
||
}
|
||
if strings.Contains(value, "年") && !strings.Contains(value, "月") {
|
||
return month * 12
|
||
}
|
||
return month
|
||
}
|
||
|
||
func appNormalizeRenewDurationOptions(values []int) []int {
|
||
seen := map[int]struct{}{}
|
||
options := make([]int, 0, len(values))
|
||
for _, month := range values {
|
||
if month <= 0 {
|
||
continue
|
||
}
|
||
if _, exists := seen[month]; exists {
|
||
continue
|
||
}
|
||
seen[month] = struct{}{}
|
||
options = append(options, month)
|
||
}
|
||
return options
|
||
}
|
||
|
||
func appNormalizeRenewYearOptions(values []int) []int {
|
||
values = appNormalizeRenewDurationOptions(values)
|
||
if len(values) == 0 {
|
||
return nil
|
||
}
|
||
legacyMonthConfig := true
|
||
for _, value := range values {
|
||
if value < 12 || value%12 != 0 {
|
||
legacyMonthConfig = false
|
||
break
|
||
}
|
||
}
|
||
if !legacyMonthConfig {
|
||
return values
|
||
}
|
||
years := make([]int, 0, len(values))
|
||
for _, month := range values {
|
||
if month > 0 && month%12 == 0 {
|
||
years = append(years, month/12)
|
||
}
|
||
}
|
||
return appNormalizeRenewDurationOptions(years)
|
||
}
|
||
|
||
func (s *sAppCard) resolveRenewUnitPrice(ctx context.Context, user *entity.SysUser, card *appCardQueryRow, pkg *entity.TSimFlowPackageInfo) (*appRenewPriceSnapshot, error) {
|
||
if s.isTerminalUser(ctx, user) {
|
||
return s.resolveTerminalRenewUnitPrice(ctx, card, pkg)
|
||
}
|
||
return s.resolveAgentRenewUnitPrice(ctx, user, card, pkg)
|
||
}
|
||
|
||
func (s *sAppCard) resolveTerminalRenewUnitPrice(ctx context.Context, card *appCardQueryRow, pkg *entity.TSimFlowPackageInfo) (*appRenewPriceSnapshot, error) {
|
||
if pkg == nil {
|
||
return nil, gerror.New("续费套餐不存在")
|
||
}
|
||
rule := appRenewSystemPriceRule(pkg)
|
||
snapshot := &appRenewPriceSnapshot{
|
||
UnitPrice: appRenewApplyPriceRule(appRenewSystemBaseCosts(pkg).TerminalCost, rule.TerminalCostRatio, rule.TerminalCostProfit),
|
||
PriceSource: "system",
|
||
}
|
||
|
||
targetAgentId := appTerminalBindDeptId(card)
|
||
if targetAgentId <= 0 {
|
||
return snapshot, nil
|
||
}
|
||
|
||
// 定价表遵循管理平台格式:dept_id 是价格归属方,target_agent_id 是当前终端卡所属渠道客户。
|
||
sellerDeptId, err := s.renewPriceOwnerDeptIdByDept(ctx, card.TenantId, targetAgentId)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if sellerDeptId <= 0 {
|
||
return snapshot, nil
|
||
}
|
||
|
||
salePrice, err := s.resolveAppRenewEffectiveSalePrice(ctx, card.TenantId, sellerDeptId, targetAgentId, pkg)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
snapshot.UnitPrice = salePrice.TerminalSalePrice
|
||
snapshot.PriceSource = appRenewTerminalPriceSource(salePrice.Rule.PriceSource)
|
||
snapshot.AgentDefaultPriceId = salePrice.Rule.AgentDefaultPriceId
|
||
snapshot.TargetPriceId = salePrice.Rule.TargetPriceId
|
||
return snapshot, nil
|
||
}
|
||
|
||
func (s *sAppCard) resolveAgentRenewUnitPrice(ctx context.Context, user *entity.SysUser, card *appCardQueryRow, pkg *entity.TSimFlowPackageInfo) (*appRenewPriceSnapshot, error) {
|
||
if pkg == nil {
|
||
return nil, gerror.New("续费套餐不存在")
|
||
}
|
||
customerAgentId := appCardOwnerDeptId(card)
|
||
if customerAgentId <= 0 {
|
||
return nil, gerror.New("卡片归属机构为空,暂不能续费")
|
||
}
|
||
snapshot := &appRenewPriceSnapshot{
|
||
UnitPrice: appRenewApplyPriceRule(pkg.CostPrice, pkg.SysDefaultRatio, pkg.SysDefaultProfit),
|
||
PriceSource: "system",
|
||
}
|
||
if user == nil || user.DeptId <= 0 {
|
||
return snapshot, nil
|
||
}
|
||
priceOwnerDeptId, err := s.renewPriceOwnerDeptIdByDept(ctx, card.TenantId, customerAgentId)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
salePrice, err := s.resolveAppRenewEffectiveSalePrice(ctx, card.TenantId, priceOwnerDeptId, customerAgentId, pkg)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
snapshot.UnitPrice = salePrice.AgentShipmentPrice
|
||
snapshot.PriceSource = salePrice.Rule.PriceSource
|
||
snapshot.AgentDefaultPriceId = salePrice.Rule.AgentDefaultPriceId
|
||
snapshot.TargetPriceId = salePrice.Rule.TargetPriceId
|
||
return snapshot, nil
|
||
}
|
||
|
||
func (s *sAppCard) resolveAppRenewEffectiveSalePrice(ctx context.Context, tenantId string, ownerDeptId int64, targetDeptId int64, pkg *entity.TSimFlowPackageInfo) (appRenewEffectiveSalePrice, error) {
|
||
baseCosts, err := s.resolveRenewEffectiveBaseCosts(ctx, tenantId, ownerDeptId, pkg, make(map[int64]struct{}))
|
||
if err != nil {
|
||
return appRenewEffectiveSalePrice{}, err
|
||
}
|
||
rule, err := s.resolveAppRenewPriceRule(ctx, tenantId, ownerDeptId, targetDeptId, pkg)
|
||
if err != nil {
|
||
return appRenewEffectiveSalePrice{}, err
|
||
}
|
||
return appRenewEffectiveSalePriceFromBaseCosts(baseCosts, rule), nil
|
||
}
|
||
|
||
func appRenewEffectiveSalePriceFromBaseCosts(baseCosts appRenewEffectiveBaseCosts, rule appRenewPriceRule) appRenewEffectiveSalePrice {
|
||
return appRenewEffectiveSalePrice{
|
||
Rule: rule,
|
||
AgentShipmentPrice: appRenewApplyPriceRule(baseCosts.AgentCost, rule.AgentCostRatio, rule.AgentCostProfit),
|
||
TerminalSalePrice: appRenewApplyPriceRule(baseCosts.TerminalCost, rule.TerminalCostRatio, rule.TerminalCostProfit),
|
||
}
|
||
}
|
||
|
||
func (s *sAppCard) resolveRenewEffectiveBaseCosts(ctx context.Context, tenantId string, deptId int64, pkg *entity.TSimFlowPackageInfo, visited map[int64]struct{}) (appRenewEffectiveBaseCosts, error) {
|
||
if pkg == nil || strings.TrimSpace(pkg.PackageCode) == "" {
|
||
return appRenewEffectiveBaseCosts{}, gerror.New("套餐编码不能为空")
|
||
}
|
||
if deptId <= 0 {
|
||
return appRenewSystemBaseCosts(pkg), nil
|
||
}
|
||
if _, exists := visited[deptId]; exists {
|
||
return appRenewEffectiveBaseCosts{}, gerror.New("组织定价层级存在循环,无法计算生效成本")
|
||
}
|
||
visited[deptId] = struct{}{}
|
||
|
||
parentDeptId, err := s.renewPriceOwnerDeptIdByDept(ctx, tenantId, deptId)
|
||
if err != nil {
|
||
return appRenewEffectiveBaseCosts{}, err
|
||
}
|
||
if parentDeptId <= 0 {
|
||
return appRenewSystemBaseCosts(pkg), nil
|
||
}
|
||
|
||
parentCosts, err := s.resolveRenewEffectiveBaseCosts(ctx, tenantId, parentDeptId, pkg, visited)
|
||
if err != nil {
|
||
return appRenewEffectiveBaseCosts{}, err
|
||
}
|
||
rule, err := s.resolveAppRenewPriceRule(ctx, tenantId, parentDeptId, deptId, pkg)
|
||
if err != nil {
|
||
return appRenewEffectiveBaseCosts{}, err
|
||
}
|
||
return appRenewEffectiveBaseCosts{
|
||
AgentCost: appRenewApplyPriceRule(parentCosts.AgentCost, rule.AgentCostRatio, rule.AgentCostProfit),
|
||
TerminalCost: appRenewApplyPriceRule(parentCosts.TerminalCost, rule.TerminalCostRatio, rule.TerminalCostProfit),
|
||
}, nil
|
||
}
|
||
|
||
func appRenewSystemBaseCosts(pkg *entity.TSimFlowPackageInfo) appRenewEffectiveBaseCosts {
|
||
terminalCost := pkg.TerminalCost
|
||
if terminalCost <= 0 {
|
||
terminalCost = pkg.CostPrice
|
||
}
|
||
return appRenewEffectiveBaseCosts{
|
||
AgentCost: appRoundAmount(pkg.CostPrice),
|
||
TerminalCost: appRoundAmount(terminalCost),
|
||
}
|
||
}
|
||
|
||
func (s *sAppCard) resolveAppRenewPriceRule(ctx context.Context, tenantId string, ownerDeptId int64, targetDeptId int64, pkg *entity.TSimFlowPackageInfo) (appRenewPriceRule, error) {
|
||
rule := appRenewSystemPriceRule(pkg)
|
||
if ownerDeptId <= 0 {
|
||
return rule, nil
|
||
}
|
||
|
||
if targetDeptId > 0 {
|
||
targetPrice, err := s.loadAppRenewPriceOverride(ctx, tenantId, ownerDeptId, 1, targetDeptId, pkg.PackageCode)
|
||
if err != nil {
|
||
return rule, err
|
||
}
|
||
if targetPrice != nil {
|
||
return appRenewPriceRuleFromOverride("target", targetPrice), nil
|
||
}
|
||
}
|
||
|
||
defaultPrice, err := s.loadAppRenewPriceOverride(ctx, tenantId, ownerDeptId, 0, 0, pkg.PackageCode)
|
||
if err != nil {
|
||
return rule, err
|
||
}
|
||
if defaultPrice != nil {
|
||
return appRenewPriceRuleFromOverride("agentDefault", defaultPrice), nil
|
||
}
|
||
return rule, nil
|
||
}
|
||
|
||
func appRenewSystemPriceRule(pkg *entity.TSimFlowPackageInfo) appRenewPriceRule {
|
||
return appRenewPriceRule{
|
||
PriceSource: "system",
|
||
AgentCostRatio: appNormalizePriceRatio(pkg.SysDefaultRatio),
|
||
AgentCostProfit: pkg.SysDefaultProfit,
|
||
TerminalCostRatio: appNormalizePriceRatio(pkg.SysDefaultTerminalRatio),
|
||
TerminalCostProfit: pkg.SysDefaultTerminalProfit,
|
||
}
|
||
}
|
||
|
||
func appRenewPriceRuleFromOverride(priceSource string, price *entity.TSimFlowPackagePrice) appRenewPriceRule {
|
||
if price == nil {
|
||
return appRenewPriceRule{PriceSource: priceSource}
|
||
}
|
||
return appRenewPriceRule{
|
||
PriceSource: priceSource,
|
||
AgentDefaultPriceId: appRenewDefaultPriceId(priceSource, price.Id),
|
||
TargetPriceId: appRenewTargetPriceId(priceSource, price.Id),
|
||
AgentCostRatio: appNormalizePriceRatio(price.AgentCostRatio),
|
||
AgentCostProfit: price.AgentCostProfit,
|
||
TerminalCostRatio: appNormalizePriceRatio(price.UserCostRatio),
|
||
TerminalCostProfit: price.UserCostProfit,
|
||
}
|
||
}
|
||
|
||
func appRenewDefaultPriceId(priceSource string, priceId int64) int64 {
|
||
if priceSource == "agentDefault" {
|
||
return priceId
|
||
}
|
||
return 0
|
||
}
|
||
|
||
func appRenewTargetPriceId(priceSource string, priceId int64) int64 {
|
||
if priceSource == "target" {
|
||
return priceId
|
||
}
|
||
return 0
|
||
}
|
||
|
||
func appRenewTerminalPriceSource(priceSource string) string {
|
||
if priceSource == "agentDefault" {
|
||
return "agentDefaultTerminal"
|
||
}
|
||
if priceSource == "target" {
|
||
return "targetTerminal"
|
||
}
|
||
return priceSource
|
||
}
|
||
|
||
func appRenewApplyPriceRule(baseCost float64, ratio float64, profit float64) float64 {
|
||
return appRoundAmount(baseCost*appNormalizePriceRatio(ratio) + profit)
|
||
}
|
||
|
||
func (s *sAppCard) renewSellerDeptId(ctx context.Context, user *entity.SysUser) (int64, error) {
|
||
if user == nil || user.DeptId <= 0 {
|
||
return 0, nil
|
||
}
|
||
return s.renewSellerDeptIdByDept(ctx, user.TenantId, user.DeptId)
|
||
}
|
||
|
||
func (s *sAppCard) renewSellerDeptIdByDept(ctx context.Context, tenantId string, deptId int64) (int64, error) {
|
||
dept, err := s.loadAppRenewDept(ctx, tenantId, deptId)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
if dept != nil && dept.ParentId > 0 {
|
||
return dept.ParentId, nil
|
||
}
|
||
return deptId, nil
|
||
}
|
||
|
||
// renewPriceOwnerDeptIdByDept 与控制台续费定价一致:定价归属方始终为客户部门的直接上级。
|
||
func (s *sAppCard) renewPriceOwnerDeptIdByDept(ctx context.Context, tenantId string, deptId int64) (int64, error) {
|
||
dept, err := s.loadAppRenewDept(ctx, tenantId, deptId)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
if dept == nil || dept.DeptId == 0 {
|
||
return 0, gerror.New("代理组织不存在")
|
||
}
|
||
return dept.ParentId, nil
|
||
}
|
||
|
||
func (s *sAppCard) loadAppRenewDept(ctx context.Context, tenantId string, deptId int64) (*entity.SysDept, error) {
|
||
if deptId <= 0 {
|
||
return nil, nil
|
||
}
|
||
var dept *entity.SysDept
|
||
err := dao.SysDept.Ctx(ctx).
|
||
Where(dao.SysDept.Columns().TenantId, tenantId).
|
||
Where(dao.SysDept.Columns().DeptId, deptId).
|
||
Limit(1).
|
||
Scan(&dept)
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, "查询代理归属失败")
|
||
}
|
||
return dept, nil
|
||
}
|
||
|
||
func (s *sAppCard) loadAppRenewPriceOverride(ctx context.Context, tenantId string, deptId int64, scopeType int, targetAgentId int64, packageCode string) (*entity.TSimFlowPackagePrice, error) {
|
||
var price *entity.TSimFlowPackagePrice
|
||
err := dao.TSimFlowPackagePrice.Ctx(ctx).
|
||
Where(dao.TSimFlowPackagePrice.Columns().TenantId, tenantId).
|
||
Where(dao.TSimFlowPackagePrice.Columns().DeptId, deptId).
|
||
Where(dao.TSimFlowPackagePrice.Columns().AgentScopeType, scopeType).
|
||
Where(dao.TSimFlowPackagePrice.Columns().TargetAgentId, targetAgentId).
|
||
Where(dao.TSimFlowPackagePrice.Columns().PackageCode, packageCode).
|
||
Limit(1).
|
||
Scan(&price)
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, "查询续费定价失败")
|
||
}
|
||
if price == nil || price.Id == 0 {
|
||
return nil, nil
|
||
}
|
||
return price, nil
|
||
}
|
||
|
||
func appTerminalBindDeptId(card *appCardQueryRow) int64 {
|
||
return appCardOwnerDeptId(card)
|
||
}
|
||
|
||
func (s *sAppCard) renewOrderParty(ctx context.Context, user *entity.SysUser, card *appCardQueryRow) (*appRenewOrderParty, error) {
|
||
if user == nil || card == nil {
|
||
return nil, gerror.New("续费订单归属信息不完整")
|
||
}
|
||
if s.isTerminalUser(ctx, user) {
|
||
priceOwnerDeptId := appTerminalBindDeptId(card)
|
||
if priceOwnerDeptId <= 0 {
|
||
return nil, gerror.New("卡片归属代理商为空,暂不能续费")
|
||
}
|
||
return &appRenewOrderParty{
|
||
CustomerType: consts.CustomerTypeTerminal,
|
||
CustomerAgentId: priceOwnerDeptId,
|
||
EndUserId: user.UserId,
|
||
AgentId: priceOwnerDeptId,
|
||
CreatedDept: user.DeptId,
|
||
}, nil
|
||
}
|
||
if user.DeptId <= 0 {
|
||
return nil, gerror.New("当前用户未绑定机构,暂不能续费")
|
||
}
|
||
customerAgentId := appCardOwnerDeptId(card)
|
||
if customerAgentId <= 0 {
|
||
return nil, gerror.New("卡片归属机构为空,暂不能续费")
|
||
}
|
||
return &appRenewOrderParty{
|
||
CustomerType: consts.CustomerTypeAgent,
|
||
CustomerAgentId: customerAgentId,
|
||
EndUserId: 0,
|
||
AgentId: user.DeptId,
|
||
CreatedDept: user.DeptId,
|
||
}, nil
|
||
}
|
||
|
||
func (s *sAppCard) lockRenewCardForSubmit(ctx context.Context, tx gdb.TX, card *appCardQueryRow, expectedCurrentPackageCode string) (*entity.TSimCardInfo, error) {
|
||
if card == nil || card.Id <= 0 {
|
||
return nil, gerror.New("卡信息不存在或无权续费")
|
||
}
|
||
var locked *entity.TSimCardInfo
|
||
err := dao.TSimCardInfo.Ctx(ctx).TX(tx).
|
||
Where(dao.TSimCardInfo.Columns().Id, card.Id).
|
||
LockUpdate().
|
||
Scan(&locked)
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, "锁定卡信息失败")
|
||
}
|
||
if locked == nil || locked.Id == 0 {
|
||
return nil, gerror.New("卡信息不存在或已变化,请刷新后重试")
|
||
}
|
||
if strings.TrimSpace(locked.TenantId) != strings.TrimSpace(card.TenantId) ||
|
||
strings.TrimSpace(locked.Iccid) != strings.TrimSpace(card.Iccid) {
|
||
return nil, gerror.New("卡片归属已变化,请刷新后重试")
|
||
}
|
||
if card.TerminalBindId > 0 || card.TerminalUserId > 0 {
|
||
bind, err := s.lockRenewTerminalBindForSubmit(ctx, tx, card)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
bindDeptId := bind.DeptId
|
||
if bindDeptId <= 0 {
|
||
bindDeptId = locked.DeptId
|
||
}
|
||
if bindDeptId != appTerminalBindDeptId(card) {
|
||
return nil, gerror.New("卡片归属已变化,请刷新后重试")
|
||
}
|
||
bindPackageCode := strings.TrimSpace(bind.CurrentPackageCode)
|
||
if bindPackageCode == "" {
|
||
bindPackageCode = strings.TrimSpace(locked.CurrentPackageCode)
|
||
}
|
||
if bindPackageCode != strings.TrimSpace(expectedCurrentPackageCode) {
|
||
return nil, gerror.New("当前卡片套餐已变化,请刷新后重试")
|
||
}
|
||
card.TerminalDeptId = bind.DeptId
|
||
card.TerminalCurrentPackageCode = strings.TrimSpace(bind.CurrentPackageCode)
|
||
} else if locked.DeptId != card.DeptId {
|
||
return nil, gerror.New("卡片归属已变化,请刷新后重试")
|
||
} else if strings.TrimSpace(locked.CurrentPackageCode) != strings.TrimSpace(expectedCurrentPackageCode) {
|
||
return nil, gerror.New("当前卡片套餐已变化,请刷新后重试")
|
||
}
|
||
card.TSimCardInfo = *locked
|
||
return locked, nil
|
||
}
|
||
|
||
func (s *sAppCard) lockRenewTerminalBindForSubmit(ctx context.Context, tx gdb.TX, card *appCardQueryRow) (*appSimTerminalRenewLockRow, error) {
|
||
var bind *appSimTerminalRenewLockRow
|
||
mod := dao.TSimTerminal.Ctx(ctx).TX(tx).
|
||
Where(dao.TSimTerminal.Columns().TenantId, card.TenantId).
|
||
Where(dao.TSimTerminal.Columns().Iccid, card.Iccid)
|
||
if card.TerminalBindId > 0 {
|
||
mod = mod.Where(dao.TSimTerminal.Columns().Id, card.TerminalBindId)
|
||
} else {
|
||
mod = mod.Where(dao.TSimTerminal.Columns().TerminalId, card.TerminalUserId)
|
||
}
|
||
err := mod.LockUpdate().Limit(1).Scan(&bind)
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, "锁定终端卡关联失败")
|
||
}
|
||
if bind == nil || bind.Id == 0 {
|
||
return nil, gerror.New("终端卡关联已变化,请刷新后重试")
|
||
}
|
||
if strings.TrimSpace(bind.TenantId) != strings.TrimSpace(card.TenantId) ||
|
||
strings.TrimSpace(bind.Iccid) != strings.TrimSpace(card.Iccid) ||
|
||
bind.TerminalId != card.TerminalUserId {
|
||
return nil, gerror.New("终端卡关联已变化,请刷新后重试")
|
||
}
|
||
return bind, nil
|
||
}
|
||
|
||
func (s *sAppCard) insertAppRenewOrder(ctx context.Context, tx gdb.TX, user *entity.SysUser, party *appRenewOrderParty, buyerNote string, orderSn string, totalAmount float64, now *gtime.Time) (int64, error) {
|
||
orderId, err := dao.TOrder.Ctx(ctx).TX(tx).Data(do.TOrder{
|
||
OrderSn: orderSn,
|
||
ParentOrderId: 0,
|
||
ParentOrderSn: "",
|
||
CustomerType: party.CustomerType,
|
||
CustomerAgentId: party.CustomerAgentId,
|
||
EndUserId: party.EndUserId,
|
||
AgentId: party.AgentId,
|
||
OrderType: consts.OrderTypeCardSubscribe,
|
||
OrderStatus: consts.OrderStatusCompleted,
|
||
ShippingStatus: 0,
|
||
ReturnStatus: 0,
|
||
Consignee: "",
|
||
Mobile: "",
|
||
Email: "",
|
||
Address: "",
|
||
AreaCode: "",
|
||
BuyerNote: strings.TrimSpace(buyerNote),
|
||
AdminNote: "",
|
||
Remark: "小程序套餐续费余额扣款订单",
|
||
ShippingMethod: consts.ShippingMethodNone,
|
||
LogisticsId: 0,
|
||
LogisticsName: "",
|
||
ShippingTypeId: 0,
|
||
ShippingTypeName: "",
|
||
TrackingNo: "",
|
||
PayType: consts.PayTypeBalance,
|
||
PayStatus: consts.PayStatusPaid,
|
||
PayOrderSn: "",
|
||
PayTime: now,
|
||
TotalAmount: totalAmount,
|
||
ProductAmount: totalAmount,
|
||
ShippingFee: 0,
|
||
ServiceFee: 0,
|
||
InvoiceFee: 0,
|
||
CouponAmount: 0,
|
||
DiscountAmount: 0,
|
||
Balance: totalAmount,
|
||
PaidAmount: totalAmount,
|
||
OnlinePaidAmount: 0,
|
||
OfflinePaidAmount: 0,
|
||
UnpaidAmount: 0,
|
||
UnrefundAmount: 0,
|
||
IsNeedCommission: 0,
|
||
DistributionStatus: 0,
|
||
TenantId: user.TenantId,
|
||
CreatedDept: party.CreatedDept,
|
||
CreatedBy: user.UserId,
|
||
CreatedAt: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}).InsertAndGetId()
|
||
if err != nil {
|
||
return 0, gerror.Wrap(err, "创建续费订单失败")
|
||
}
|
||
return orderId, nil
|
||
}
|
||
|
||
func appRenewServicePeriod(expireAt *gtime.Time, months int) (*gtime.Time, *gtime.Time) {
|
||
beginAt := appRenewBeginDateFromExpire(expireAt)
|
||
if beginAt == nil || beginAt.IsZero() {
|
||
beginAt = appRenewBeginDateFromExpire(gtime.Now())
|
||
}
|
||
return beginAt, beginAt.AddDate(0, months, 0)
|
||
}
|
||
|
||
func appRenewSubmitExpireAt(lockedCard *entity.TSimCardInfo, renewCtx *appRenewPackageContext) *gtime.Time {
|
||
var bind *entity.TSimCardBindPkg
|
||
if renewCtx != nil {
|
||
bind = renewCtx.LatestBind
|
||
}
|
||
return appRenewBaseExpireTimeFromEntity(lockedCard, bind)
|
||
}
|
||
|
||
func appRenewMarshalJSON(value any) string {
|
||
data, err := json.Marshal(value)
|
||
if err != nil {
|
||
return "{}"
|
||
}
|
||
return string(data)
|
||
}
|
||
|
||
func appRenewFormatTime(value *gtime.Time) string {
|
||
if value == nil || value.IsZero() {
|
||
return ""
|
||
}
|
||
return value.Format("Y-m-d H:i:s")
|
||
}
|
||
|
||
func appContainsInt(values []int, target int) bool {
|
||
for _, value := range values {
|
||
if value == target {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func appRenewRecommendIndex(durations []int) int {
|
||
if len(durations) == 0 {
|
||
return -1
|
||
}
|
||
return len(durations) - 1
|
||
}
|
||
|
||
func appRenewDurationSelectionFromParam(durationYears int, durationMonths int, options []int) appRenewDurationSelection {
|
||
years := durationYears
|
||
if years <= 0 {
|
||
years = appRenewDurationYearsFromLegacy(durationMonths, options)
|
||
}
|
||
if years <= 0 {
|
||
return appRenewDurationSelection{}
|
||
}
|
||
return appRenewDurationSelection{
|
||
Years: years,
|
||
Months: appRenewActualDurationMonths(years),
|
||
}
|
||
}
|
||
|
||
func appRenewDurationYearsFromLegacy(durationMonths int, options []int) int {
|
||
if durationMonths <= 0 {
|
||
return 0
|
||
}
|
||
options = appNormalizeRenewYearOptions(options)
|
||
if appContainsInt(options, durationMonths) {
|
||
return durationMonths
|
||
}
|
||
if durationMonths%12 == 0 && appContainsInt(options, durationMonths/12) {
|
||
return durationMonths / 12
|
||
}
|
||
if durationMonths%12 == 0 {
|
||
return durationMonths / 12
|
||
}
|
||
return durationMonths
|
||
}
|
||
|
||
func appRenewActualDurationMonths(years int) int {
|
||
if years <= 0 {
|
||
return 0
|
||
}
|
||
return years * 12
|
||
}
|
||
|
||
func appRenewTotalAmount(unitPrice float64, durationYears int, quantity int) float64 {
|
||
if quantity <= 0 {
|
||
quantity = 1
|
||
}
|
||
return appRoundAmount(unitPrice * float64(durationYears) * float64(quantity))
|
||
}
|
||
|
||
func appRenewOptionModel(durationYears int, unitPrice float64, totalAmount float64, expireAfter *gtime.Time, recommended bool) *model.AppRenewOptionModel {
|
||
durationMonths := appRenewActualDurationMonths(durationYears)
|
||
return &model.AppRenewOptionModel{
|
||
DurationMonths: durationMonths,
|
||
ActualDurationMonths: durationMonths,
|
||
DurationYears: durationYears,
|
||
DurationValue: durationYears,
|
||
DurationUnit: appRenewDurationUnitYear,
|
||
DurationUnitText: appRenewDurationUnitYearText,
|
||
Label: appRenewDurationYearLabel(durationYears),
|
||
UnitPrice: unitPrice,
|
||
TotalAmount: totalAmount,
|
||
PriceText: fmt.Sprintf("¥%.2f", totalAmount),
|
||
ExpireAfter: expireAfter,
|
||
Recommended: recommended,
|
||
}
|
||
}
|
||
|
||
func appRenewDurationYearLabel(years int) string {
|
||
if years <= 0 {
|
||
return ""
|
||
}
|
||
return fmt.Sprintf("%d 年", years)
|
||
}
|
||
|
||
func appRenewDurationLabel(months int) string {
|
||
if months%12 == 0 {
|
||
years := months / 12
|
||
if years == 1 {
|
||
return "1 年"
|
||
}
|
||
return fmt.Sprintf("%d 年", years)
|
||
}
|
||
return fmt.Sprintf("%d 个月", months)
|
||
}
|
||
|
||
func appRenewExpireAfter(expireAt *gtime.Time, months int) *gtime.Time {
|
||
base := appRenewBeginDateFromExpire(expireAt)
|
||
if base == nil || base.IsZero() {
|
||
base = appRenewBeginDateFromExpire(gtime.Now())
|
||
}
|
||
return base.AddDate(0, months, 0)
|
||
}
|
||
|
||
func appRenewEffectiveRule(expireAt *gtime.Time) string {
|
||
if expireAt != nil && !expireAt.IsZero() && expireAt.After(gtime.Now()) {
|
||
return "当前套餐到期后顺延"
|
||
}
|
||
return "按当前套餐到期日顺延"
|
||
}
|
||
|
||
func appRenewBaseExpireTimeFromRow(card *appCardQueryRow, bind *entity.TSimCardBindPkg) *gtime.Time {
|
||
var expireAt *gtime.Time
|
||
if bind != nil && bind.ExpectedExpireTime != nil && !bind.ExpectedExpireTime.IsZero() {
|
||
expireAt = bind.ExpectedExpireTime
|
||
}
|
||
if card == nil || card.FlowPkgEndAt == nil || card.FlowPkgEndAt.IsZero() {
|
||
return expireAt
|
||
}
|
||
if expireAt == nil || card.FlowPkgEndAt.After(expireAt) {
|
||
return card.FlowPkgEndAt
|
||
}
|
||
return expireAt
|
||
}
|
||
|
||
func appRenewBaseExpireTimeFromEntity(card *entity.TSimCardInfo, bind *entity.TSimCardBindPkg) *gtime.Time {
|
||
var expireAt *gtime.Time
|
||
if bind != nil && bind.ExpectedExpireTime != nil && !bind.ExpectedExpireTime.IsZero() {
|
||
expireAt = bind.ExpectedExpireTime
|
||
}
|
||
if card == nil || card.FlowPkgEndAt == nil || card.FlowPkgEndAt.IsZero() {
|
||
return expireAt
|
||
}
|
||
if expireAt == nil || card.FlowPkgEndAt.After(expireAt) {
|
||
return card.FlowPkgEndAt
|
||
}
|
||
return expireAt
|
||
}
|
||
|
||
func appRenewBeginDateFromExpire(expireAt *gtime.Time) *gtime.Time {
|
||
if expireAt == nil || expireAt.IsZero() {
|
||
return nil
|
||
}
|
||
t := expireAt.Time
|
||
return gtime.NewFromTime(time.Date(t.Year(), t.Month(), 0, 23, 59, 59, 0, t.Location()))
|
||
}
|
||
|
||
func appRenewExpireAfterAllowedWindow(expireAt *gtime.Time) bool {
|
||
if expireAt == nil || expireAt.IsZero() {
|
||
return false
|
||
}
|
||
return expireAt.After(gtime.Now().AddDate(0, 6, 0))
|
||
}
|
||
|
||
func appNormalizePriceRatio(ratio float64) float64 {
|
||
if ratio > 10 {
|
||
return ratio / 100
|
||
}
|
||
return ratio
|
||
}
|
||
|
||
func appRoundAmount(value float64) float64 {
|
||
return math.Round(value*100) / 100
|
||
}
|