chore: 初始开源发布
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),属既有缺陷,本仓未引入亦未修复。
This commit is contained in:
@@ -0,0 +1,660 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user