2bec257289
iotlcm-worker-svr:异步执行中枢(RabbitMQ 消费者 + 全量同步编排)。 - Go module:iotlcm-worker-svr → git.5iot.com/iotlcm-public/iotlcm-worker-svr(214 文件) - 凭据与内部标识脱敏:MySQL/Redis 凭据、私网与生产 IP、内网域名、 库名 xiujie_admin → iotlcm、哨兵名占位 - 159 处真实手机号 → 合法 11 位编造号(保留号段与后 4 位,维持互异, 不破坏「校验 MSISDN 格式」等测试断言) - 移除内部资产:CI 触发器、Jenkins 触发测试文档、预演门禁与镜像 digest 记录 - Dockerfile 改用公共基础镜像(golang:1.25-alpine → alpine:3.23), 移除写死的 git 凭据 - README 重写(架构/全量同步模型/配置/已知失败),补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes 已知:3 个测试包在源仓同一提交上同样失败(client/upstream、 service/allocate、service/inbound),属既有缺陷,本仓未引入亦未修复。
661 lines
23 KiB
Go
661 lines
23 KiB
Go
package renewchange
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gogf/gf/v2/encoding/gjson"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/os/gtime"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/do"
|
|
rc "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/renewchange"
|
|
)
|
|
|
|
// ExecutionContract is the confirmed renew/change contract persisted in
|
|
// batch_main.batch_info. DurationMonths is the actual persisted service
|
|
// duration in months; DurationDisplayValue is the value selected by the user.
|
|
type ExecutionContract struct {
|
|
BatchNo string
|
|
BatchType int
|
|
SourceBatchNo string
|
|
TenantID string
|
|
CustomerAgentID int64
|
|
SellerAgentID int64
|
|
CreatedDeptID int64
|
|
OperatorUserID int64
|
|
PackageCode string
|
|
PackageName string
|
|
PriceSource string
|
|
AgentDefaultPriceID int64
|
|
TargetPriceID int64
|
|
UnitPrice float64
|
|
Quantity int
|
|
BillingMethod int
|
|
RenewDurationUnit string
|
|
RenewDurationUnitText string
|
|
DurationDisplayValue int
|
|
DurationMonths int
|
|
GiftDurationMonths int
|
|
OfflineContractCompleted bool
|
|
PaymentMode string
|
|
PayType int
|
|
PayStatus int
|
|
PayAmount float64
|
|
BalanceAmount float64
|
|
OfflinePaidAmount float64
|
|
BalanceDetailSerialNo string
|
|
PaymentTime string
|
|
PayerDeptID int64
|
|
PayerUserID int64
|
|
BuyerNote string
|
|
AdminNote string
|
|
Remark string
|
|
EffectiveMode string
|
|
RenewDate string
|
|
PackageBeginTime *gtime.Time
|
|
}
|
|
|
|
type OrderProductContract struct {
|
|
ID int64
|
|
Code string
|
|
Name string
|
|
Type int
|
|
}
|
|
|
|
// ParseExecutionContract reads the canonical top-level contract and falls
|
|
// back to the prepare snapshots only for batches created before the flat
|
|
// contract migration.
|
|
func ParseExecutionContract(main *rc.BatchMainRow) (*ExecutionContract, error) {
|
|
if main == nil {
|
|
return nil, gerror.New("batch main cannot be nil")
|
|
}
|
|
if main.BatchType != consts.BatchTypeRenew && main.BatchType != consts.BatchTypeChange {
|
|
return nil, gerror.Newf("unsupported renew/change batch type: %d", main.BatchType)
|
|
}
|
|
if main.BatchInfo == nil || main.BatchInfo.IsNil() {
|
|
return nil, gerror.New("confirmed batch contract is missing")
|
|
}
|
|
|
|
var snapshotPath, packageCodeField, packageNameField, unitPriceField string
|
|
if main.BatchType == consts.BatchTypeRenew {
|
|
snapshotPath = "priceSnapshot"
|
|
packageCodeField = "packageCode"
|
|
packageNameField = "packageName"
|
|
unitPriceField = "renewUnitPrice"
|
|
} else {
|
|
snapshotPath = "changePriceSnapshot"
|
|
packageCodeField = "newPackageCode"
|
|
packageNameField = "newPackageName"
|
|
unitPriceField = "changeUnitPrice"
|
|
}
|
|
|
|
info := main.BatchInfo
|
|
duration := executionDurationFromInfo(info, snapshotPath)
|
|
if main.BatchType == consts.BatchTypeRenew {
|
|
duration = normalizeRenewExecutionDuration(info, duration)
|
|
}
|
|
contract := &ExecutionContract{
|
|
BatchNo: strings.TrimSpace(main.BatchNo),
|
|
BatchType: main.BatchType,
|
|
SourceBatchNo: executionSourceBatchNo(main, info),
|
|
TenantID: strings.TrimSpace(main.TenantId),
|
|
CustomerAgentID: firstInt64(info, "customerAgentId", snapshotPath+".customerAgentId"),
|
|
SellerAgentID: main.DeptId,
|
|
CreatedDeptID: main.CreatedDept,
|
|
OperatorUserID: main.CreatedBy,
|
|
PackageCode: firstString(info, packageCodeField, snapshotPath+"."+packageCodeField),
|
|
PackageName: firstString(info, packageNameField, snapshotPath+"."+packageNameField),
|
|
PriceSource: firstString(info, "priceSource", snapshotPath+".priceSource"),
|
|
AgentDefaultPriceID: firstInt64(info, "agentDefaultPriceId", snapshotPath+".agentDefaultPriceId"),
|
|
TargetPriceID: firstInt64(info, "targetPriceId", snapshotPath+".targetPriceId"),
|
|
Quantity: firstInt(info, "quantity"),
|
|
BillingMethod: duration.billingMethod,
|
|
RenewDurationUnit: duration.unit,
|
|
RenewDurationUnitText: duration.unitText,
|
|
DurationDisplayValue: duration.displayValue,
|
|
DurationMonths: duration.actualMonths,
|
|
GiftDurationMonths: duration.giftMonths,
|
|
OfflineContractCompleted: info.Get("offlineContractCompleted").Bool(),
|
|
PaymentMode: firstString(info, "paymentMode", "paymentSnapshot.paymentMode"),
|
|
PayType: firstInt(info, "payType", "paymentSnapshot.payType"),
|
|
PayStatus: firstInt(info, "payStatus", "paymentSnapshot.payStatus"),
|
|
PayAmount: firstAmount(info, "payAmount", "paymentSnapshot.payAmount"),
|
|
BalanceAmount: firstAmount(info, "balanceAmount", "paymentSnapshot.balanceAmount"),
|
|
OfflinePaidAmount: firstAmount(info, "offlinePaidAmount", "paymentSnapshot.offlinePaidAmount"),
|
|
BalanceDetailSerialNo: firstString(info, "balanceDetailSerialNo", "paymentSnapshot.balanceDetailSerialNo"),
|
|
PaymentTime: firstString(info, "paymentTime", "paymentSnapshot.paymentTime"),
|
|
PayerDeptID: firstInt64(info, "payerDeptId", "paymentSnapshot.payerDeptId"),
|
|
PayerUserID: firstInt64(info, "payerUserId", "paymentSnapshot.payerUserId"),
|
|
BuyerNote: strings.TrimSpace(info.Get("buyerNote").String()),
|
|
AdminNote: strings.TrimSpace(info.Get("adminNote").String()),
|
|
Remark: strings.TrimSpace(info.Get("remark").String()),
|
|
EffectiveMode: strings.TrimSpace(info.Get("effectiveMode").String()),
|
|
RenewDate: strings.TrimSpace(info.Get("renewDate").String()),
|
|
}
|
|
if value, ok := firstFloat64(info, unitPriceField, snapshotPath+"."+unitPriceField); ok {
|
|
contract.UnitPrice = value
|
|
} else {
|
|
contract.UnitPrice = -1
|
|
}
|
|
if contract.SellerAgentID <= 0 {
|
|
contract.SellerAgentID = main.CreatedDept
|
|
}
|
|
if contract.CreatedDeptID <= 0 {
|
|
contract.CreatedDeptID = contract.SellerAgentID
|
|
}
|
|
contract.normalizePaymentContract()
|
|
if raw := strings.TrimSpace(info.Get("packageBeginTime").String()); raw != "" {
|
|
beginAt, err := parseExecutionTime(raw)
|
|
if err != nil {
|
|
return nil, gerror.Wrap(err, "parse package begin time failed")
|
|
}
|
|
contract.PackageBeginTime = beginAt
|
|
}
|
|
if err := contract.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
return contract, nil
|
|
}
|
|
|
|
func executionSourceBatchNo(main *rc.BatchMainRow, info *gjson.Json) string {
|
|
if main == nil || main.SourceType != consts.BatchSourceTypeBatchSelect {
|
|
return ""
|
|
}
|
|
sourceBatchNo := firstString(info, "sourceBatchNo", "changePrepareSnapshot.sourceBatchNo")
|
|
if sourceBatchNo == "" || sourceBatchNo == strings.TrimSpace(main.BatchNo) {
|
|
sourceBatchNo = strings.TrimSpace(main.SourceBatchNo)
|
|
}
|
|
if sourceBatchNo == strings.TrimSpace(main.BatchNo) {
|
|
return ""
|
|
}
|
|
return sourceBatchNo
|
|
}
|
|
|
|
type executionDurationContract struct {
|
|
billingMethod int
|
|
unit string
|
|
unitText string
|
|
displayValue int
|
|
actualMonths int
|
|
giftMonths int
|
|
}
|
|
|
|
func executionDurationFromInfo(info *gjson.Json, snapshotPath string) executionDurationContract {
|
|
rawDurationMonths := firstInt(info, "durationMonths")
|
|
displayValue := firstInt(info, "durationValue", "durationYears")
|
|
hasDisplayValue := hasJSONValue(info, "durationValue") || hasJSONValue(info, "durationYears")
|
|
if displayValue <= 0 {
|
|
displayValue = rawDurationMonths
|
|
}
|
|
|
|
billingMethod := firstInt(info, "billingMethod", snapshotPath+".billingMethod")
|
|
unit := firstString(info, "renewDurationUnit", snapshotPath+".renewDurationUnit")
|
|
if strings.TrimSpace(unit) == "" {
|
|
if hasDisplayValue {
|
|
unit = renewDurationUnitForBillingMethod(billingMethod)
|
|
} else {
|
|
unit = consts.RenewDurationUnitMonth
|
|
}
|
|
}
|
|
unit = consts.NormalizeRenewDurationUnit(unit)
|
|
unitText := firstString(info, "renewDurationUnitText", snapshotPath+".renewDurationUnitText")
|
|
if strings.TrimSpace(unitText) == "" {
|
|
unitText = consts.RenewDurationUnitText(unit)
|
|
}
|
|
|
|
actualMonths := firstInt(info, "actualDurationMonths")
|
|
if actualMonths <= 0 {
|
|
if hasDisplayValue {
|
|
actualMonths = consts.DurationValueToMonths(unit, displayValue)
|
|
} else {
|
|
actualMonths = rawDurationMonths
|
|
}
|
|
}
|
|
giftMonths := firstInt(info, "actualGiftDurationMonths")
|
|
if giftMonths <= 0 {
|
|
giftMonths = firstInt(info, "giftDurationMonths")
|
|
}
|
|
if hasDisplayValue && giftMonths > 0 && firstInt(info, "actualGiftDurationMonths") <= 0 {
|
|
giftMonths = consts.DurationValueToMonths(unit, giftMonths)
|
|
}
|
|
return executionDurationContract{
|
|
billingMethod: billingMethod,
|
|
unit: unit,
|
|
unitText: unitText,
|
|
displayValue: displayValue,
|
|
actualMonths: actualMonths,
|
|
giftMonths: giftMonths,
|
|
}
|
|
}
|
|
|
|
func normalizeRenewExecutionDuration(info *gjson.Json, duration executionDurationContract) executionDurationContract {
|
|
displayValue := firstInt(info, "durationValue", "durationYears")
|
|
if displayValue <= 0 {
|
|
displayValue = normalizeLegacyRenewDurationValue(firstInt(info, "durationMonths"))
|
|
}
|
|
if displayValue <= 0 {
|
|
return duration
|
|
}
|
|
duration.unit = consts.RenewDurationUnitYear
|
|
duration.unitText = consts.RenewDurationUnitText(duration.unit)
|
|
duration.displayValue = displayValue
|
|
duration.actualMonths = consts.DurationValueToMonths(duration.unit, displayValue)
|
|
return duration
|
|
}
|
|
|
|
func (c *ExecutionContract) Validate() error {
|
|
if c == nil {
|
|
return gerror.New("execution contract cannot be nil")
|
|
}
|
|
if strings.TrimSpace(c.BatchNo) == "" {
|
|
return gerror.New("execution contract batch number is missing")
|
|
}
|
|
if c.BatchType != consts.BatchTypeRenew && c.BatchType != consts.BatchTypeChange {
|
|
return gerror.New("execution contract batch type is invalid")
|
|
}
|
|
if strings.TrimSpace(c.TenantID) == "" {
|
|
return gerror.New("execution contract tenant id is missing")
|
|
}
|
|
if c.CustomerAgentID <= 0 || c.SellerAgentID <= 0 || c.CreatedDeptID <= 0 || c.OperatorUserID <= 0 {
|
|
return gerror.New("execution contract organization or operator is incomplete")
|
|
}
|
|
if strings.TrimSpace(c.PackageCode) == "" || strings.TrimSpace(c.PackageName) == "" {
|
|
return gerror.New("execution contract package is incomplete")
|
|
}
|
|
if c.UnitPrice < 0 {
|
|
return gerror.New("execution contract unit price is missing")
|
|
}
|
|
if c.Quantity <= 0 {
|
|
return gerror.New("execution contract quantity must be greater than zero")
|
|
}
|
|
if c.DurationDisplayValue <= 0 {
|
|
c.DurationDisplayValue = c.DurationMonths
|
|
}
|
|
if c.DurationMonths <= 0 {
|
|
return gerror.New("execution contract duration months must be greater than zero")
|
|
}
|
|
if c.GiftDurationMonths < 0 {
|
|
return gerror.New("execution contract gift duration months cannot be negative")
|
|
}
|
|
c.normalizePaymentContract()
|
|
switch strings.TrimSpace(c.PaymentMode) {
|
|
case "offline":
|
|
if !c.OfflineContractCompleted {
|
|
return gerror.New("execution contract offline confirmation is missing")
|
|
}
|
|
case "balance":
|
|
if c.PayType != consts.PayTypeBalance {
|
|
return gerror.New("execution contract balance pay type is invalid")
|
|
}
|
|
if c.PayAmount < 0 || c.BalanceAmount < 0 {
|
|
return gerror.New("execution contract balance amount is invalid")
|
|
}
|
|
totalAmount := roundAmount(c.TotalAmount())
|
|
if roundAmount(c.PayAmount) != totalAmount || roundAmount(c.BalanceAmount) != totalAmount {
|
|
return gerror.New("execution contract balance payment amount does not match total amount")
|
|
}
|
|
if strings.TrimSpace(c.BalanceDetailSerialNo) == "" && c.PayAmount > 0 {
|
|
return gerror.New("execution contract balance serial number is missing")
|
|
}
|
|
default:
|
|
return gerror.New("execution contract payment mode is invalid")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *ExecutionContract) normalizePaymentContract() {
|
|
if c == nil {
|
|
return
|
|
}
|
|
if strings.TrimSpace(c.PaymentMode) == "" {
|
|
if c.PayType == consts.PayTypeBalance || c.BalanceAmount > 0 || strings.TrimSpace(c.BalanceDetailSerialNo) != "" {
|
|
c.PaymentMode = "balance"
|
|
} else {
|
|
c.PaymentMode = "offline"
|
|
}
|
|
}
|
|
amount := c.TotalAmount()
|
|
if c.PayAmount <= 0 && amount > 0 {
|
|
c.PayAmount = amount
|
|
}
|
|
if c.PayStatus == 0 {
|
|
c.PayStatus = consts.PayStatusPaid
|
|
}
|
|
if c.PaymentMode == "offline" {
|
|
if c.PayType == 0 {
|
|
c.PayType = consts.PayTypeOffline
|
|
}
|
|
if c.OfflinePaidAmount <= 0 {
|
|
c.OfflinePaidAmount = amount
|
|
}
|
|
return
|
|
}
|
|
if c.PaymentMode == "balance" {
|
|
if c.PayType == 0 {
|
|
c.PayType = consts.PayTypeBalance
|
|
}
|
|
if c.BalanceAmount <= 0 {
|
|
c.BalanceAmount = amount
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *ExecutionContract) TotalAmount() float64 {
|
|
if c == nil {
|
|
return 0
|
|
}
|
|
return roundAmount(c.UnitPrice * float64(c.DisplayDurationValue()) * float64(c.Quantity))
|
|
}
|
|
|
|
func (c *ExecutionContract) ItemPrice() float64 {
|
|
if c == nil {
|
|
return 0
|
|
}
|
|
return roundAmount(c.UnitPrice * float64(c.DisplayDurationValue()))
|
|
}
|
|
|
|
func (c *ExecutionContract) DisplayDurationValue() int {
|
|
if c == nil {
|
|
return 0
|
|
}
|
|
if c.DurationDisplayValue > 0 {
|
|
return c.DurationDisplayValue
|
|
}
|
|
return c.DurationMonths
|
|
}
|
|
|
|
func (c *ExecutionContract) ServiceEnd(begin time.Time) time.Time {
|
|
if c == nil {
|
|
return begin
|
|
}
|
|
return addMonths(begin, c.DurationMonths+c.GiftDurationMonths)
|
|
}
|
|
|
|
func BuildOrderDO(contract *ExecutionContract, orderSN string, now time.Time) (*rc.OrderDO, error) {
|
|
if err := contract.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
orderSN = strings.TrimSpace(orderSN)
|
|
if orderSN == "" {
|
|
return nil, gerror.New("order number must be generated before building the order")
|
|
}
|
|
amount := contract.TotalAmount()
|
|
balanceAmount := float64(0)
|
|
offlinePaidAmount := amount
|
|
payType := consts.PayTypeOffline
|
|
payOrderSN := ""
|
|
if contract.PaymentMode == "balance" {
|
|
balanceAmount = contract.BalanceAmount
|
|
offlinePaidAmount = 0
|
|
payType = consts.PayTypeBalance
|
|
payOrderSN = contract.BalanceDetailSerialNo
|
|
}
|
|
remark := contract.Remark
|
|
if remark == "" {
|
|
remark = "SIM card package business order"
|
|
}
|
|
payTime := gtime.NewFromTime(now)
|
|
if strings.TrimSpace(contract.PaymentTime) != "" {
|
|
if parsed, err := parseExecutionTime(strings.TrimSpace(contract.PaymentTime)); err == nil {
|
|
payTime = parsed
|
|
}
|
|
}
|
|
orderType := consts.OrderTypeCardRenew
|
|
if contract.BatchType == consts.BatchTypeChange {
|
|
orderType = consts.OrderTypeCardChange
|
|
}
|
|
return &rc.OrderDO{
|
|
OrderSn: orderSN,
|
|
ParentOrderId: int64(0),
|
|
ParentOrderSn: "",
|
|
CustomerType: consts.CustomerTypeAgent,
|
|
CustomerAgentId: contract.CustomerAgentID,
|
|
EndUserId: int64(0),
|
|
AgentId: contract.SellerAgentID,
|
|
OrderType: orderType,
|
|
OrderStatus: consts.OrderStatusCompleted,
|
|
ShippingStatus: 0,
|
|
ReturnStatus: consts.OrderReturnStatusNone,
|
|
Consignee: "",
|
|
Mobile: "",
|
|
Email: "",
|
|
Address: "",
|
|
AreaCode: "",
|
|
BuyerNote: contract.BuyerNote,
|
|
AdminNote: contract.AdminNote,
|
|
Remark: remark,
|
|
ShippingMethod: consts.ShippingMethodNone,
|
|
LogisticsId: int64(0),
|
|
LogisticsName: "",
|
|
ShippingTypeId: int64(0),
|
|
ShippingTypeName: "",
|
|
TrackingNo: "",
|
|
PayType: payType,
|
|
PayStatus: consts.PayStatusPaid,
|
|
PayOrderSn: payOrderSN,
|
|
PayTime: payTime,
|
|
TotalAmount: amount,
|
|
ProductAmount: amount,
|
|
ShippingFee: 0,
|
|
ServiceFee: 0,
|
|
InvoiceFee: 0,
|
|
CouponAmount: 0,
|
|
DiscountAmount: 0,
|
|
Balance: balanceAmount,
|
|
PaidAmount: amount,
|
|
OnlinePaidAmount: 0,
|
|
OfflinePaidAmount: offlinePaidAmount,
|
|
UnpaidAmount: 0,
|
|
UnrefundAmount: 0,
|
|
IsNeedCommission: 0,
|
|
DistributionStatus: 0,
|
|
TenantId: contract.TenantID,
|
|
CreatedDept: contract.CreatedDeptID,
|
|
CreatedBy: contract.OperatorUserID,
|
|
CreatedAt: payTime,
|
|
UpdatedBy: contract.OperatorUserID,
|
|
UpdatedAt: payTime,
|
|
}, nil
|
|
}
|
|
|
|
func BuildOrderItemDO(contract *ExecutionContract, product OrderProductContract, orderID int64, orderSN string, beginAt, endAt, now time.Time) (*rc.OrderItemDO, error) {
|
|
if err := contract.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
if orderID <= 0 || strings.TrimSpace(orderSN) == "" {
|
|
return nil, gerror.New("order item requires a persisted order id and order number")
|
|
}
|
|
if product.ID <= 0 || strings.TrimSpace(product.Code) == "" || strings.TrimSpace(product.Name) == "" {
|
|
return nil, gerror.New("order product contract is incomplete")
|
|
}
|
|
if strings.TrimSpace(product.Code) != strings.TrimSpace(contract.PackageCode) {
|
|
return nil, gerror.New("order product does not match the confirmed package")
|
|
}
|
|
if !endAt.After(beginAt) {
|
|
return nil, gerror.New("order service end time must be after begin time")
|
|
}
|
|
|
|
skuData, err := buildSKUData(contract, product, beginAt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
extraSKUData, err := buildExtraSKUData(contract, beginAt, endAt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &rc.OrderItemDO{
|
|
OrderId: orderID,
|
|
OrderSn: strings.TrimSpace(orderSN),
|
|
CustomerType: consts.CustomerTypeAgent,
|
|
CustomerAgentId: contract.CustomerAgentID,
|
|
EndUserId: int64(0),
|
|
Price: contract.ItemPrice(),
|
|
Quantity: contract.Quantity,
|
|
ProductId: product.ID,
|
|
ProductName: product.Name,
|
|
ProductSn: product.Code,
|
|
PicThumb: "",
|
|
SkuId: int64(0),
|
|
SkuData: skuData,
|
|
DeliveryQuantity: 0,
|
|
ProductType: consts.ProductTypeCard,
|
|
IsGift: 0,
|
|
AgentId: contract.SellerAgentID,
|
|
PrepayPrice: 0,
|
|
Commission: "",
|
|
OriginPrice: contract.ItemPrice(),
|
|
PromotionData: "",
|
|
ExtraSkuData: extraSKUData,
|
|
PayMode: consts.PayModeYearMonth,
|
|
ServiceBeginTime: gtime.NewFromTime(beginAt),
|
|
ServiceEndTime: gtime.NewFromTime(endAt),
|
|
TenantId: contract.TenantID,
|
|
CreatedDept: contract.CreatedDeptID,
|
|
CreatedBy: contract.OperatorUserID,
|
|
CreatedAt: gtime.NewFromTime(now),
|
|
UpdatedBy: contract.OperatorUserID,
|
|
UpdatedAt: gtime.NewFromTime(now),
|
|
}, nil
|
|
}
|
|
|
|
// BuildRenewCardUpdate returns the intentionally narrow card update used by
|
|
// renew execution. GoFrame ignores every nil field in this generated DO.
|
|
func BuildRenewCardUpdate(contract *ExecutionContract, endAt time.Time, packageStatus int, now time.Time) (*do.TSimCardInfo, error) {
|
|
if err := contract.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
if contract.BatchType != consts.BatchTypeRenew {
|
|
return nil, gerror.New("renew card update requires a renew contract")
|
|
}
|
|
if endAt.IsZero() {
|
|
return nil, gerror.New("renew card end time cannot be empty")
|
|
}
|
|
if now.IsZero() {
|
|
return nil, gerror.New("renew card update time cannot be empty")
|
|
}
|
|
if packageStatus < consts.PackageStatusPending || packageStatus > consts.PackageStatusExpired {
|
|
return nil, gerror.New("renew card package status is invalid")
|
|
}
|
|
return &do.TSimCardInfo{
|
|
FlowPkgEndAt: gtime.NewFromTime(endAt),
|
|
PackageStatus: packageStatus,
|
|
UpdatedBy: contract.OperatorUserID,
|
|
UpdatedAt: gtime.NewFromTime(now),
|
|
}, nil
|
|
}
|
|
|
|
func buildSKUData(contract *ExecutionContract, product OrderProductContract, beginAt time.Time) (string, error) {
|
|
durationLabel := fmt.Sprintf("%d months", contract.DurationMonths)
|
|
if contract.DurationDisplayValue > 0 && strings.TrimSpace(contract.RenewDurationUnit) != "" {
|
|
durationLabel = renewDurationLabel(contract.DurationDisplayValue, contract.RenewDurationUnit)
|
|
}
|
|
data := []map[string]any{
|
|
{"skuName": "package", "skuValue": product.Name, "packageCode": product.Code, "packageType": product.Type},
|
|
{"skuName": "quantity", "skuValue": fmt.Sprintf("%d", contract.Quantity)},
|
|
{"skuName": "effectiveTime", "skuValue": beginAt.Format("2006-01-02 15:04:05")},
|
|
{"skuName": "duration", "skuValue": durationLabel},
|
|
}
|
|
encoded, err := json.Marshal(data)
|
|
if err != nil {
|
|
return "", gerror.Wrap(err, "marshal order SKU data failed")
|
|
}
|
|
return string(encoded), nil
|
|
}
|
|
|
|
func buildExtraSKUData(contract *ExecutionContract, beginAt, endAt time.Time) (string, error) {
|
|
data := map[string]any{
|
|
"outboundBatchNo": contract.BatchNo,
|
|
"businessBatchNo": contract.BatchNo,
|
|
"offlineContractCompleted": contract.OfflineContractCompleted,
|
|
"packageBeginTime": beginAt.Format("2006-01-02 15:04:05"),
|
|
"packageEndTime": endAt.Format("2006-01-02 15:04:05"),
|
|
"durationMonths": contract.DurationMonths,
|
|
"durationValue": contract.DisplayDurationValue(),
|
|
"actualDurationMonths": contract.DurationMonths,
|
|
"durationUnit": strings.TrimSpace(contract.RenewDurationUnit),
|
|
"billingMethod": contract.BillingMethod,
|
|
"giftDurationMonths": contract.GiftDurationMonths,
|
|
"unitPrice": contract.UnitPrice,
|
|
"amount": contract.TotalAmount(),
|
|
"paymentMode": contract.PaymentMode,
|
|
"payType": contract.PayType,
|
|
"balanceAmount": contract.BalanceAmount,
|
|
"offlinePaidAmount": contract.OfflinePaidAmount,
|
|
}
|
|
if contract.BalanceDetailSerialNo != "" {
|
|
data["balanceDetailSerialNo"] = contract.BalanceDetailSerialNo
|
|
}
|
|
if contract.SourceBatchNo != "" {
|
|
data["sourceBatchNo"] = contract.SourceBatchNo
|
|
}
|
|
if contract.EffectiveMode != "" {
|
|
data["effectiveMode"] = contract.EffectiveMode
|
|
}
|
|
encoded, err := json.Marshal(data)
|
|
if err != nil {
|
|
return "", gerror.Wrap(err, "marshal order extra SKU data failed")
|
|
}
|
|
return string(encoded), nil
|
|
}
|
|
|
|
func firstString(info *gjson.Json, paths ...string) string {
|
|
for _, path := range paths {
|
|
value := strings.TrimSpace(info.Get(path).String())
|
|
if value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func hasJSONValue(info *gjson.Json, path string) bool {
|
|
if info == nil || strings.TrimSpace(path) == "" {
|
|
return false
|
|
}
|
|
value := info.Get(path)
|
|
return value != nil && !value.IsNil()
|
|
}
|
|
|
|
func firstInt(info *gjson.Json, paths ...string) int {
|
|
return int(firstInt64(info, paths...))
|
|
}
|
|
|
|
func firstInt64(info *gjson.Json, paths ...string) int64 {
|
|
for _, path := range paths {
|
|
value := info.Get(path)
|
|
if value != nil && !value.IsNil() {
|
|
return value.Int64()
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func firstFloat64(info *gjson.Json, paths ...string) (float64, bool) {
|
|
for _, path := range paths {
|
|
value := info.Get(path)
|
|
if value != nil && !value.IsNil() {
|
|
return value.Float64(), true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func firstAmount(info *gjson.Json, paths ...string) float64 {
|
|
value, _ := firstFloat64(info, paths...)
|
|
return roundAmount(value)
|
|
}
|
|
|
|
func parseExecutionTime(value string) (*gtime.Time, error) {
|
|
for _, layout := range []string{"2006-01-02 15:04:05", "2006-01-02"} {
|
|
parsed, err := time.ParseInLocation(layout, value, time.Local)
|
|
if err == nil {
|
|
return gtime.NewFromTime(parsed), nil
|
|
}
|
|
}
|
|
return nil, gerror.Newf("invalid execution time: %s", value)
|
|
}
|