修复(#79): 合并internal并适配数量配价
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -2,7 +2,6 @@ package order
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
|
||||
commonLogic "github.com/perfect-panel/server/internal/logic/common"
|
||||
"github.com/perfect-panel/server/internal/model/order"
|
||||
@@ -115,14 +114,28 @@ func (l *PreCreateOrderLogic) PreCreateOrder(req *types.PurchaseOrderRequest) (r
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeNewUserOnly), "not a new user")
|
||||
}
|
||||
|
||||
var discount float64 = 1
|
||||
if len(newUserDiscount.Discounts) > 0 {
|
||||
discount = getDiscount(newUserDiscount.Discounts, req.Quantity, newUserDiscount.EligibleForDiscount)
|
||||
priceResult, err := calculatePurchasePrice(
|
||||
l.ctx,
|
||||
l.svcCtx,
|
||||
u.Id,
|
||||
targetSubscribeID,
|
||||
sub.UnitPrice,
|
||||
req.Quantity,
|
||||
newUserDiscount.Discounts,
|
||||
newUserDiscount.EligibleForDiscount,
|
||||
!isSingleModeRenewal,
|
||||
)
|
||||
if err != nil {
|
||||
l.Errorw("[PreCreateOrder] Promo price calculation error",
|
||||
logger.Field("error", err.Error()),
|
||||
logger.Field("user_id", u.Id),
|
||||
logger.Field("subscribe_id", targetSubscribeID),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
price := sub.UnitPrice * req.Quantity
|
||||
|
||||
amount := int64(math.Round(float64(price) * discount))
|
||||
discountAmount := price - amount
|
||||
price := priceResult.OriginalPrice
|
||||
amount := priceResult.PayableBase
|
||||
discountAmount := priceResult.DiscountAmount
|
||||
var couponAmount int64
|
||||
if req.Coupon != "" {
|
||||
couponInfo, err := l.svcCtx.CouponModel.FindOneByCode(l.ctx, req.Coupon)
|
||||
@@ -185,6 +198,7 @@ func (l *PreCreateOrderLogic) PreCreateOrder(req *types.PurchaseOrderRequest) (r
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
Discount: discountAmount,
|
||||
PromoDiscount: priceResult.PromoDiscount,
|
||||
GiftAmount: deductionAmount,
|
||||
Coupon: req.Coupon,
|
||||
CouponDiscount: couponAmount,
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
|
||||
commonLogic "github.com/perfect-panel/server/internal/logic/common"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
)
|
||||
|
||||
type orderPriceResult struct {
|
||||
OriginalPrice int64
|
||||
PayableBase int64
|
||||
DiscountAmount int64
|
||||
PromoRuleId int64
|
||||
PromoDiscount int64
|
||||
PromoPrice int64
|
||||
}
|
||||
|
||||
func calculatePurchasePrice(
|
||||
ctx context.Context,
|
||||
svcCtx *svc.ServiceContext,
|
||||
userID int64,
|
||||
subscribeID int64,
|
||||
unitPrice int64,
|
||||
quantity int64,
|
||||
discounts []types.SubscribeDiscount,
|
||||
eligibleForDiscount bool,
|
||||
allowPromo bool,
|
||||
) (*orderPriceResult, error) {
|
||||
originalPrice := unitPrice * quantity
|
||||
result := &orderPriceResult{
|
||||
OriginalPrice: originalPrice,
|
||||
PayableBase: originalPrice,
|
||||
}
|
||||
|
||||
if allowPromo {
|
||||
promoResult, err := commonLogic.EvaluatePromo(ctx, svcCtx, userID, subscribeID, quantity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if promoResult != nil && promoResult.Eligible && promoResult.PromoPrice < unitPrice {
|
||||
result.PayableBase = promoResult.PromoPrice * quantity
|
||||
result.PromoRuleId = promoResult.RuleID
|
||||
result.PromoDiscount = originalPrice - result.PayableBase
|
||||
result.PromoPrice = promoResult.PromoPrice
|
||||
if result.PromoDiscount < 0 {
|
||||
result.PromoDiscount = 0
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
discount := float64(1)
|
||||
if len(discounts) > 0 {
|
||||
discount = getDiscount(discounts, quantity, eligibleForDiscount)
|
||||
}
|
||||
result.PayableBase = int64(math.Round(float64(originalPrice) * discount))
|
||||
result.DiscountAmount = originalPrice - result.PayableBase
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/promo"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type fakePromoModel struct {
|
||||
rules []*promo.RuleWithPrice
|
||||
}
|
||||
|
||||
func (m fakePromoModel) QueryEligibleRules(context.Context, int64, int64) ([]*promo.RuleWithPrice, error) {
|
||||
return m.rules, nil
|
||||
}
|
||||
|
||||
func (m fakePromoModel) InsertUsage(context.Context, *promo.Usage, ...*gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m fakePromoModel) InsertRule(context.Context, *promo.Rule) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m fakePromoModel) FindRule(context.Context, int64) (*promo.Rule, error) {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
func (m fakePromoModel) UpdateRule(context.Context, *promo.Rule) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m fakePromoModel) DeleteRule(context.Context, int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m fakePromoModel) QueryRuleList(context.Context, int, int, string, *bool, string) (int64, []*promo.Rule, error) {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
func (m fakePromoModel) UpsertPrices(context.Context, int64, []*promo.SubscribePromo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m fakePromoModel) FindPrice(context.Context, int64) (*promo.SubscribePromo, error) {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
func (m fakePromoModel) DeletePrice(context.Context, int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m fakePromoModel) QueryPriceList(context.Context, int64, int, int) (int64, []*promo.SubscribePromo, error) {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
func (m fakePromoModel) QueryUsageList(context.Context, promo.UsageFilter) (int64, []*promo.Usage, error) {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
func (m fakePromoModel) Transaction(context.Context, func(*gorm.DB) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCalculatePurchasePricePromoSkipsPercentDiscount(t *testing.T) {
|
||||
svcCtx := &svc.ServiceContext{
|
||||
DB: &gorm.DB{},
|
||||
PromoModel: fakePromoModel{rules: []*promo.RuleWithPrice{
|
||||
{
|
||||
Rule: promo.Rule{
|
||||
Id: 9,
|
||||
Name: "campaign",
|
||||
Type: promo.RuleTypeCampaign,
|
||||
Enabled: true,
|
||||
},
|
||||
PromoPrice: 600,
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
result, err := calculatePurchasePrice(
|
||||
context.Background(),
|
||||
svcCtx,
|
||||
1,
|
||||
2,
|
||||
1000,
|
||||
3,
|
||||
[]types.SubscribeDiscount{{Quantity: 3, Discount: 50}},
|
||||
true,
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("calculatePurchasePrice returned error: %v", err)
|
||||
}
|
||||
|
||||
if result.OriginalPrice != 3000 {
|
||||
t.Fatalf("OriginalPrice = %d, want 3000", result.OriginalPrice)
|
||||
}
|
||||
if result.PayableBase != 1800 {
|
||||
t.Fatalf("PayableBase = %d, want 1800", result.PayableBase)
|
||||
}
|
||||
if result.DiscountAmount != 0 {
|
||||
t.Fatalf("DiscountAmount = %d, want 0", result.DiscountAmount)
|
||||
}
|
||||
if result.PromoRuleId != 9 {
|
||||
t.Fatalf("PromoRuleId = %d, want 9", result.PromoRuleId)
|
||||
}
|
||||
if result.PromoDiscount != 1200 {
|
||||
t.Fatalf("PromoDiscount = %d, want 1200", result.PromoDiscount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculatePurchasePriceIgnoresInvalidPromoPrice(t *testing.T) {
|
||||
svcCtx := &svc.ServiceContext{
|
||||
DB: &gorm.DB{},
|
||||
PromoModel: fakePromoModel{rules: []*promo.RuleWithPrice{
|
||||
{
|
||||
Rule: promo.Rule{
|
||||
Id: 10,
|
||||
Name: "invalid campaign",
|
||||
Type: promo.RuleTypeCampaign,
|
||||
Enabled: true,
|
||||
},
|
||||
PromoPrice: 1000,
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
result, err := calculatePurchasePrice(
|
||||
context.Background(),
|
||||
svcCtx,
|
||||
1,
|
||||
2,
|
||||
1000,
|
||||
3,
|
||||
[]types.SubscribeDiscount{{Quantity: 3, Discount: 50}},
|
||||
true,
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("calculatePurchasePrice returned error: %v", err)
|
||||
}
|
||||
|
||||
if result.PayableBase != 1500 {
|
||||
t.Fatalf("PayableBase = %d, want 1500", result.PayableBase)
|
||||
}
|
||||
if result.DiscountAmount != 1500 {
|
||||
t.Fatalf("DiscountAmount = %d, want 1500", result.DiscountAmount)
|
||||
}
|
||||
if result.PromoRuleId != 0 || result.PromoDiscount != 0 {
|
||||
t.Fatalf("promo fields = (%d, %d), want (0, 0)", result.PromoRuleId, result.PromoDiscount)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package order
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -204,14 +203,28 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var discount float64 = 1
|
||||
if len(newUserDiscount.Discounts) > 0 {
|
||||
discount = getDiscount(newUserDiscount.Discounts, req.Quantity, newUserDiscount.EligibleForDiscount)
|
||||
priceResult, err := calculatePurchasePrice(
|
||||
l.ctx,
|
||||
l.svcCtx,
|
||||
u.Id,
|
||||
targetSubscribeID,
|
||||
sub.UnitPrice,
|
||||
req.Quantity,
|
||||
newUserDiscount.Discounts,
|
||||
newUserDiscount.EligibleForDiscount,
|
||||
orderType == 1,
|
||||
)
|
||||
if err != nil {
|
||||
l.Errorw("[Purchase] Promo price calculation error",
|
||||
logger.Field("error", err.Error()),
|
||||
logger.Field("user_id", u.Id),
|
||||
logger.Field("subscribe_id", targetSubscribeID),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
price := sub.UnitPrice * req.Quantity
|
||||
// discount amount
|
||||
amount := int64(math.Round(float64(price) * discount))
|
||||
discountAmount := price - amount
|
||||
price := priceResult.OriginalPrice
|
||||
amount := priceResult.PayableBase
|
||||
discountAmount := priceResult.DiscountAmount
|
||||
|
||||
// Validate amount to prevent overflow
|
||||
if amount > MaxOrderAmount {
|
||||
@@ -306,6 +319,8 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
Discount: discountAmount,
|
||||
PromoRuleId: priceResult.PromoRuleId,
|
||||
PromoDiscount: priceResult.PromoDiscount,
|
||||
GiftAmount: deductionAmount,
|
||||
Coupon: req.Coupon,
|
||||
CouponDiscount: coupon,
|
||||
|
||||
Reference in New Issue
Block a user