feat: 增加订单过滤功能,支持按内容ID、支付时间范围和支付金额范围筛选
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"quyun/v2/app/requests"
|
"quyun/v2/app/requests"
|
||||||
"quyun/v2/database/models"
|
"quyun/v2/database/models"
|
||||||
"quyun/v2/pkg/consts"
|
"quyun/v2/pkg/consts"
|
||||||
@@ -12,8 +14,18 @@ type AdminOrderListFilter struct {
|
|||||||
requests.Pagination `json:",inline" query:",inline"`
|
requests.Pagination `json:",inline" query:",inline"`
|
||||||
// UserID filters orders by buyer user id.
|
// UserID filters orders by buyer user id.
|
||||||
UserID *int64 `json:"user_id,omitempty" query:"user_id"`
|
UserID *int64 `json:"user_id,omitempty" query:"user_id"`
|
||||||
|
// ContentID filters orders by purchased content id (via order_items join).
|
||||||
|
ContentID *int64 `json:"content_id,omitempty" query:"content_id"`
|
||||||
// Status filters orders by order status.
|
// Status filters orders by order status.
|
||||||
Status *consts.OrderStatus `json:"status,omitempty" query:"status"`
|
Status *consts.OrderStatus `json:"status,omitempty" query:"status"`
|
||||||
|
// PaidAtFrom filters orders by paid_at >= this time.
|
||||||
|
PaidAtFrom *time.Time `json:"paid_at_from,omitempty" query:"paid_at_from"`
|
||||||
|
// PaidAtTo filters orders by paid_at <= this time.
|
||||||
|
PaidAtTo *time.Time `json:"paid_at_to,omitempty" query:"paid_at_to"`
|
||||||
|
// AmountPaidMin filters orders by amount_paid >= this amount (cents).
|
||||||
|
AmountPaidMin *int64 `json:"amount_paid_min,omitempty" query:"amount_paid_min"`
|
||||||
|
// AmountPaidMax filters orders by amount_paid <= this amount (cents).
|
||||||
|
AmountPaidMax *int64 `json:"amount_paid_max,omitempty" query:"amount_paid_max"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminOrderRefundForm defines payload for tenant-admin to refund an order.
|
// AdminOrderRefundForm defines payload for tenant-admin to refund an order.
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"quyun/v2/app/requests"
|
"quyun/v2/app/requests"
|
||||||
"quyun/v2/pkg/consts"
|
"quyun/v2/pkg/consts"
|
||||||
)
|
)
|
||||||
@@ -11,4 +13,10 @@ type MyOrderListFilter struct {
|
|||||||
requests.Pagination `json:",inline" query:",inline"`
|
requests.Pagination `json:",inline" query:",inline"`
|
||||||
// Status filters orders by order status.
|
// Status filters orders by order status.
|
||||||
Status *consts.OrderStatus `json:"status,omitempty" query:"status"`
|
Status *consts.OrderStatus `json:"status,omitempty" query:"status"`
|
||||||
|
// PaidAtFrom filters orders by paid_at >= this time.
|
||||||
|
PaidAtFrom *time.Time `json:"paid_at_from,omitempty" query:"paid_at_from"`
|
||||||
|
// PaidAtTo filters orders by paid_at <= this time.
|
||||||
|
PaidAtTo *time.Time `json:"paid_at_to,omitempty" query:"paid_at_to"`
|
||||||
|
// ContentID filters orders by purchased content id (via order_items join).
|
||||||
|
ContentID *int64 `json:"content_id,omitempty" query:"content_id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,41 +23,73 @@ import (
|
|||||||
"go.ipao.vip/gen/types"
|
"go.ipao.vip/gen/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PurchaseOrderSnapshot 为“内容购买订单”的下单快照(用于历史展示与争议审计)。
|
||||||
type PurchaseOrderSnapshot struct {
|
type PurchaseOrderSnapshot struct {
|
||||||
|
// ContentID 内容ID。
|
||||||
ContentID int64 `json:"content_id"`
|
ContentID int64 `json:"content_id"`
|
||||||
|
// ContentTitle 内容标题(下单时快照,避免事后改名影响历史订单展示)。
|
||||||
ContentTitle string `json:"content_title"`
|
ContentTitle string `json:"content_title"`
|
||||||
|
// ContentUserID 内容作者用户ID(用于审计与后续分成扩展)。
|
||||||
ContentUserID int64 `json:"content_user_id"`
|
ContentUserID int64 `json:"content_user_id"`
|
||||||
|
// ContentVisibility 下单时的可见性快照。
|
||||||
ContentVisibility consts.ContentVisibility `json:"content_visibility"`
|
ContentVisibility consts.ContentVisibility `json:"content_visibility"`
|
||||||
|
// PreviewSeconds 下单时的试看秒数快照。
|
||||||
PreviewSeconds int32 `json:"preview_seconds"`
|
PreviewSeconds int32 `json:"preview_seconds"`
|
||||||
|
// PreviewDownloadable 下单时的试看是否可下载快照(当前固定为 false)。
|
||||||
PreviewDownloadable bool `json:"preview_downloadable"`
|
PreviewDownloadable bool `json:"preview_downloadable"`
|
||||||
|
// Currency 币种:当前固定 CNY(金额单位为分)。
|
||||||
Currency consts.Currency `json:"currency"`
|
Currency consts.Currency `json:"currency"`
|
||||||
|
// PriceAmount 基础价格(分)。
|
||||||
PriceAmount int64 `json:"price_amount"`
|
PriceAmount int64 `json:"price_amount"`
|
||||||
|
// DiscountType 折扣类型(none/percent/amount)。
|
||||||
DiscountType consts.DiscountType `json:"discount_type"`
|
DiscountType consts.DiscountType `json:"discount_type"`
|
||||||
|
// DiscountValue 折扣值(percent=0-100;amount=分)。
|
||||||
DiscountValue int64 `json:"discount_value"`
|
DiscountValue int64 `json:"discount_value"`
|
||||||
|
// DiscountStartAt 折扣开始时间(可选)。
|
||||||
DiscountStartAt *time.Time `json:"discount_start_at,omitempty"`
|
DiscountStartAt *time.Time `json:"discount_start_at,omitempty"`
|
||||||
|
// DiscountEndAt 折扣结束时间(可选)。
|
||||||
DiscountEndAt *time.Time `json:"discount_end_at,omitempty"`
|
DiscountEndAt *time.Time `json:"discount_end_at,omitempty"`
|
||||||
|
// AmountOriginal 原价金额(分)。
|
||||||
AmountOriginal int64 `json:"amount_original"`
|
AmountOriginal int64 `json:"amount_original"`
|
||||||
|
// AmountDiscount 优惠金额(分)。
|
||||||
AmountDiscount int64 `json:"amount_discount"`
|
AmountDiscount int64 `json:"amount_discount"`
|
||||||
|
// AmountPaid 实付金额(分)。
|
||||||
AmountPaid int64 `json:"amount_paid"`
|
AmountPaid int64 `json:"amount_paid"`
|
||||||
|
// PurchaseAt 下单时间(逻辑时间)。
|
||||||
PurchaseAt time.Time `json:"purchase_at"`
|
PurchaseAt time.Time `json:"purchase_at"`
|
||||||
|
// PurchaseIdempotency 幂等键(可选)。
|
||||||
PurchaseIdempotency string `json:"purchase_idempotency_key,omitempty"`
|
PurchaseIdempotency string `json:"purchase_idempotency_key,omitempty"`
|
||||||
|
// PurchasePricingNotes 价格计算补充说明(可选,便于排查争议)。
|
||||||
PurchasePricingNotes string `json:"purchase_pricing_notes,omitempty"`
|
PurchasePricingNotes string `json:"purchase_pricing_notes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OrderItemSnapshot 为“订单明细”的内容快照。
|
||||||
type OrderItemSnapshot struct {
|
type OrderItemSnapshot struct {
|
||||||
|
// ContentID 内容ID。
|
||||||
ContentID int64 `json:"content_id"`
|
ContentID int64 `json:"content_id"`
|
||||||
|
// ContentTitle 内容标题快照。
|
||||||
ContentTitle string `json:"content_title"`
|
ContentTitle string `json:"content_title"`
|
||||||
|
// ContentUserID 内容作者用户ID。
|
||||||
ContentUserID int64 `json:"content_user_id"`
|
ContentUserID int64 `json:"content_user_id"`
|
||||||
|
// AmountPaid 该行实付金额(分)。
|
||||||
AmountPaid int64 `json:"amount_paid"`
|
AmountPaid int64 `json:"amount_paid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TopupOrderSnapshot 为“后台充值订单”的快照(用于审计与追责)。
|
||||||
type TopupOrderSnapshot struct {
|
type TopupOrderSnapshot struct {
|
||||||
|
// OperatorUserID 充值操作人用户ID(租户管理员)。
|
||||||
OperatorUserID int64 `json:"operator_user_id"`
|
OperatorUserID int64 `json:"operator_user_id"`
|
||||||
|
// TargetUserID 充值目标用户ID(租户成员)。
|
||||||
TargetUserID int64 `json:"target_user_id"`
|
TargetUserID int64 `json:"target_user_id"`
|
||||||
|
// Amount 充值金额(分)。
|
||||||
Amount int64 `json:"amount"`
|
Amount int64 `json:"amount"`
|
||||||
|
// Currency 币种:当前固定 CNY(金额单位为分)。
|
||||||
Currency consts.Currency `json:"currency"`
|
Currency consts.Currency `json:"currency"`
|
||||||
|
// Reason 充值原因(可选,强烈建议填写用于审计)。
|
||||||
Reason string `json:"reason,omitempty"`
|
Reason string `json:"reason,omitempty"`
|
||||||
|
// IdempotencyKey 幂等键(可选)。
|
||||||
IdempotencyKey string `json:"idempotency_key,omitempty"`
|
IdempotencyKey string `json:"idempotency_key,omitempty"`
|
||||||
|
// TopupAt 充值时间(逻辑时间)。
|
||||||
TopupAt time.Time `json:"topup_at"`
|
TopupAt time.Time `json:"topup_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +272,7 @@ func (s *order) MyOrderPage(
|
|||||||
"tenant_id": tenantID,
|
"tenant_id": tenantID,
|
||||||
"user_id": userID,
|
"user_id": userID,
|
||||||
"status": lo.FromPtr(filter.Status),
|
"status": lo.FromPtr(filter.Status),
|
||||||
|
"content_id": lo.FromPtr(filter.ContentID),
|
||||||
}).Info("services.order.me.page")
|
}).Info("services.order.me.page")
|
||||||
|
|
||||||
filter.Pagination.Format()
|
filter.Pagination.Format()
|
||||||
@@ -254,6 +287,18 @@ func (s *order) MyOrderPage(
|
|||||||
if filter.Status != nil {
|
if filter.Status != nil {
|
||||||
conds = append(conds, tbl.Status.Eq(*filter.Status))
|
conds = append(conds, tbl.Status.Eq(*filter.Status))
|
||||||
}
|
}
|
||||||
|
if filter.PaidAtFrom != nil {
|
||||||
|
conds = append(conds, tbl.PaidAt.Gte(*filter.PaidAtFrom))
|
||||||
|
}
|
||||||
|
if filter.PaidAtTo != nil {
|
||||||
|
conds = append(conds, tbl.PaidAt.Lte(*filter.PaidAtTo))
|
||||||
|
}
|
||||||
|
if filter.ContentID != nil && *filter.ContentID > 0 {
|
||||||
|
oiTbl, _ := models.OrderItemQuery.QueryContext(ctx)
|
||||||
|
query = query.LeftJoin(oiTbl, oiTbl.OrderID.EqCol(tbl.ID))
|
||||||
|
conds = append(conds, oiTbl.ContentID.Eq(*filter.ContentID))
|
||||||
|
query = query.Group(tbl.ID)
|
||||||
|
}
|
||||||
|
|
||||||
items, total, err := query.Where(conds...).Order(tbl.ID.Desc()).FindByPage(int(filter.Offset()), int(filter.Limit))
|
items, total, err := query.Where(conds...).Order(tbl.ID.Desc()).FindByPage(int(filter.Offset()), int(filter.Limit))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -308,6 +353,7 @@ func (s *order) AdminOrderPage(
|
|||||||
"tenant_id": tenantID,
|
"tenant_id": tenantID,
|
||||||
"user_id": lo.FromPtr(filter.UserID),
|
"user_id": lo.FromPtr(filter.UserID),
|
||||||
"status": lo.FromPtr(filter.Status),
|
"status": lo.FromPtr(filter.Status),
|
||||||
|
"content_id": lo.FromPtr(filter.ContentID),
|
||||||
}).Info("services.order.admin.page")
|
}).Info("services.order.admin.page")
|
||||||
|
|
||||||
filter.Pagination.Format()
|
filter.Pagination.Format()
|
||||||
@@ -322,6 +368,24 @@ func (s *order) AdminOrderPage(
|
|||||||
if filter.Status != nil {
|
if filter.Status != nil {
|
||||||
conds = append(conds, tbl.Status.Eq(*filter.Status))
|
conds = append(conds, tbl.Status.Eq(*filter.Status))
|
||||||
}
|
}
|
||||||
|
if filter.PaidAtFrom != nil {
|
||||||
|
conds = append(conds, tbl.PaidAt.Gte(*filter.PaidAtFrom))
|
||||||
|
}
|
||||||
|
if filter.PaidAtTo != nil {
|
||||||
|
conds = append(conds, tbl.PaidAt.Lte(*filter.PaidAtTo))
|
||||||
|
}
|
||||||
|
if filter.AmountPaidMin != nil {
|
||||||
|
conds = append(conds, tbl.AmountPaid.Gte(*filter.AmountPaidMin))
|
||||||
|
}
|
||||||
|
if filter.AmountPaidMax != nil {
|
||||||
|
conds = append(conds, tbl.AmountPaid.Lte(*filter.AmountPaidMax))
|
||||||
|
}
|
||||||
|
if filter.ContentID != nil && *filter.ContentID > 0 {
|
||||||
|
oiTbl, _ := models.OrderItemQuery.QueryContext(ctx)
|
||||||
|
query = query.LeftJoin(oiTbl, oiTbl.OrderID.EqCol(tbl.ID))
|
||||||
|
conds = append(conds, oiTbl.ContentID.Eq(*filter.ContentID))
|
||||||
|
query = query.Group(tbl.ID)
|
||||||
|
}
|
||||||
|
|
||||||
items, total, err := query.Where(conds...).Order(tbl.ID.Desc()).FindByPage(int(filter.Offset()), int(filter.Limit))
|
items, total, err := query.Where(conds...).Order(tbl.ID.Desc()).FindByPage(int(filter.Offset()), int(filter.Limit))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"quyun/v2/database/models"
|
"quyun/v2/database/models"
|
||||||
"quyun/v2/pkg/consts"
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
@@ -184,10 +185,11 @@ func (s *OrderTestSuite) Test_AdminTopupUser() {
|
|||||||
func (s *OrderTestSuite) Test_MyOrderPage() {
|
func (s *OrderTestSuite) Test_MyOrderPage() {
|
||||||
Convey("Order.MyOrderPage", s.T(), func() {
|
Convey("Order.MyOrderPage", s.T(), func() {
|
||||||
ctx := s.T().Context()
|
ctx := s.T().Context()
|
||||||
|
now := time.Now().UTC()
|
||||||
tenantID := int64(1)
|
tenantID := int64(1)
|
||||||
userID := int64(2)
|
userID := int64(2)
|
||||||
|
|
||||||
s.truncate(ctx, models.TableNameOrder, models.TableNameOrderItem)
|
s.truncate(ctx, models.TableNameOrderItem, models.TableNameOrder)
|
||||||
|
|
||||||
Convey("参数非法应返回错误", func() {
|
Convey("参数非法应返回错误", func() {
|
||||||
_, err := Order.MyOrderPage(ctx, 0, userID, &dto.MyOrderListFilter{})
|
_, err := Order.MyOrderPage(ctx, 0, userID, &dto.MyOrderListFilter{})
|
||||||
@@ -199,6 +201,64 @@ func (s *OrderTestSuite) Test_MyOrderPage() {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(pager.Total, ShouldEqual, 0)
|
So(pager.Total, ShouldEqual, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("按 content_id 过滤", func() {
|
||||||
|
o1 := &models.Order{
|
||||||
|
TenantID: tenantID,
|
||||||
|
UserID: userID,
|
||||||
|
Type: consts.OrderTypeContentPurchase,
|
||||||
|
Status: consts.OrderStatusPaid,
|
||||||
|
Currency: consts.CurrencyCNY,
|
||||||
|
AmountPaid: 100,
|
||||||
|
Snapshot: types.JSON([]byte("{}")),
|
||||||
|
PaidAt: now,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
So(o1.Create(ctx), ShouldBeNil)
|
||||||
|
So((&models.OrderItem{
|
||||||
|
TenantID: tenantID,
|
||||||
|
UserID: userID,
|
||||||
|
OrderID: o1.ID,
|
||||||
|
ContentID: 111,
|
||||||
|
ContentUserID: 1,
|
||||||
|
AmountPaid: 100,
|
||||||
|
Snapshot: types.JSON([]byte("{}")),
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}).Create(ctx), ShouldBeNil)
|
||||||
|
|
||||||
|
o2 := &models.Order{
|
||||||
|
TenantID: tenantID,
|
||||||
|
UserID: userID,
|
||||||
|
Type: consts.OrderTypeContentPurchase,
|
||||||
|
Status: consts.OrderStatusPaid,
|
||||||
|
Currency: consts.CurrencyCNY,
|
||||||
|
AmountPaid: 200,
|
||||||
|
Snapshot: types.JSON([]byte("{}")),
|
||||||
|
PaidAt: now.Add(time.Minute),
|
||||||
|
CreatedAt: now.Add(time.Minute),
|
||||||
|
UpdatedAt: now.Add(time.Minute),
|
||||||
|
}
|
||||||
|
So(o2.Create(ctx), ShouldBeNil)
|
||||||
|
So((&models.OrderItem{
|
||||||
|
TenantID: tenantID,
|
||||||
|
UserID: userID,
|
||||||
|
OrderID: o2.ID,
|
||||||
|
ContentID: 222,
|
||||||
|
ContentUserID: 1,
|
||||||
|
AmountPaid: 200,
|
||||||
|
Snapshot: types.JSON([]byte("{}")),
|
||||||
|
CreatedAt: now.Add(time.Minute),
|
||||||
|
UpdatedAt: now.Add(time.Minute),
|
||||||
|
}).Create(ctx), ShouldBeNil)
|
||||||
|
|
||||||
|
pager, err := Order.MyOrderPage(ctx, tenantID, userID, &dto.MyOrderListFilter{
|
||||||
|
ContentID: lo.ToPtr(int64(111)),
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(pager.Total, ShouldEqual, 1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,9 +285,10 @@ func (s *OrderTestSuite) Test_MyOrderDetail() {
|
|||||||
func (s *OrderTestSuite) Test_AdminOrderPage() {
|
func (s *OrderTestSuite) Test_AdminOrderPage() {
|
||||||
Convey("Order.AdminOrderPage", s.T(), func() {
|
Convey("Order.AdminOrderPage", s.T(), func() {
|
||||||
ctx := s.T().Context()
|
ctx := s.T().Context()
|
||||||
|
now := time.Now().UTC()
|
||||||
tenantID := int64(1)
|
tenantID := int64(1)
|
||||||
|
|
||||||
s.truncate(ctx, models.TableNameOrder, models.TableNameOrderItem)
|
s.truncate(ctx, models.TableNameOrderItem, models.TableNameOrder)
|
||||||
|
|
||||||
Convey("参数非法应返回错误", func() {
|
Convey("参数非法应返回错误", func() {
|
||||||
_, err := Order.AdminOrderPage(ctx, 0, &dto.AdminOrderListFilter{})
|
_, err := Order.AdminOrderPage(ctx, 0, &dto.AdminOrderListFilter{})
|
||||||
@@ -239,6 +300,102 @@ func (s *OrderTestSuite) Test_AdminOrderPage() {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(pager.Total, ShouldEqual, 0)
|
So(pager.Total, ShouldEqual, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("按 paid_at 时间窗过滤", func() {
|
||||||
|
o1 := &models.Order{
|
||||||
|
TenantID: tenantID,
|
||||||
|
UserID: 2,
|
||||||
|
Type: consts.OrderTypeContentPurchase,
|
||||||
|
Status: consts.OrderStatusPaid,
|
||||||
|
Currency: consts.CurrencyCNY,
|
||||||
|
AmountPaid: 100,
|
||||||
|
Snapshot: types.JSON([]byte("{}")),
|
||||||
|
PaidAt: now.Add(-time.Hour),
|
||||||
|
CreatedAt: now.Add(-time.Hour),
|
||||||
|
UpdatedAt: now.Add(-time.Hour),
|
||||||
|
}
|
||||||
|
So(o1.Create(ctx), ShouldBeNil)
|
||||||
|
So((&models.OrderItem{
|
||||||
|
TenantID: tenantID,
|
||||||
|
UserID: 2,
|
||||||
|
OrderID: o1.ID,
|
||||||
|
ContentID: 333,
|
||||||
|
ContentUserID: 1,
|
||||||
|
AmountPaid: 100,
|
||||||
|
Snapshot: types.JSON([]byte("{}")),
|
||||||
|
CreatedAt: now.Add(-time.Hour),
|
||||||
|
UpdatedAt: now.Add(-time.Hour),
|
||||||
|
}).Create(ctx), ShouldBeNil)
|
||||||
|
|
||||||
|
o2 := &models.Order{
|
||||||
|
TenantID: tenantID,
|
||||||
|
UserID: 3,
|
||||||
|
Type: consts.OrderTypeContentPurchase,
|
||||||
|
Status: consts.OrderStatusPaid,
|
||||||
|
Currency: consts.CurrencyCNY,
|
||||||
|
AmountPaid: 200,
|
||||||
|
Snapshot: types.JSON([]byte("{}")),
|
||||||
|
PaidAt: now,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
So(o2.Create(ctx), ShouldBeNil)
|
||||||
|
So((&models.OrderItem{
|
||||||
|
TenantID: tenantID,
|
||||||
|
UserID: 3,
|
||||||
|
OrderID: o2.ID,
|
||||||
|
ContentID: 444,
|
||||||
|
ContentUserID: 1,
|
||||||
|
AmountPaid: 200,
|
||||||
|
Snapshot: types.JSON([]byte("{}")),
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}).Create(ctx), ShouldBeNil)
|
||||||
|
|
||||||
|
from := now.Add(-10 * time.Minute)
|
||||||
|
to := now.Add(10 * time.Minute)
|
||||||
|
pager, err := Order.AdminOrderPage(ctx, tenantID, &dto.AdminOrderListFilter{
|
||||||
|
PaidAtFrom: &from,
|
||||||
|
PaidAtTo: &to,
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(pager.Total, ShouldEqual, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("按 content_id 过滤", func() {
|
||||||
|
s.truncate(ctx, models.TableNameOrderItem, models.TableNameOrder)
|
||||||
|
|
||||||
|
o1 := &models.Order{
|
||||||
|
TenantID: tenantID,
|
||||||
|
UserID: 2,
|
||||||
|
Type: consts.OrderTypeContentPurchase,
|
||||||
|
Status: consts.OrderStatusPaid,
|
||||||
|
Currency: consts.CurrencyCNY,
|
||||||
|
AmountPaid: 100,
|
||||||
|
Snapshot: types.JSON([]byte("{}")),
|
||||||
|
PaidAt: now,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
So(o1.Create(ctx), ShouldBeNil)
|
||||||
|
So((&models.OrderItem{
|
||||||
|
TenantID: tenantID,
|
||||||
|
UserID: 2,
|
||||||
|
OrderID: o1.ID,
|
||||||
|
ContentID: 555,
|
||||||
|
ContentUserID: 1,
|
||||||
|
AmountPaid: 100,
|
||||||
|
Snapshot: types.JSON([]byte("{}")),
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}).Create(ctx), ShouldBeNil)
|
||||||
|
|
||||||
|
pager, err := Order.AdminOrderPage(ctx, tenantID, &dto.AdminOrderListFilter{
|
||||||
|
ContentID: lo.ToPtr(int64(555)),
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(pager.Total, ShouldEqual, 1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -623,6 +623,24 @@ const docTemplate = `{
|
|||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "AmountPaidMax filters orders by amount_paid \u003c= this amount (cents).",
|
||||||
|
"name": "amount_paid_max",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "AmountPaidMin filters orders by amount_paid \u003e= this amount (cents).",
|
||||||
|
"name": "amount_paid_min",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "ContentID filters orders by purchased content id (via order_items join).",
|
||||||
|
"name": "content_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).",
|
"description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).",
|
||||||
@@ -635,6 +653,18 @@ const docTemplate = `{
|
|||||||
"name": "page",
|
"name": "page",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "PaidAtFrom filters orders by paid_at \u003e= this time.",
|
||||||
|
"name": "paid_at_from",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "PaidAtTo filters orders by paid_at \u003c= this time.",
|
||||||
|
"name": "paid_at_to",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"enum": [
|
"enum": [
|
||||||
"created",
|
"created",
|
||||||
@@ -1172,6 +1202,12 @@ const docTemplate = `{
|
|||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "ContentID filters orders by purchased content id (via order_items join).",
|
||||||
|
"name": "content_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).",
|
"description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).",
|
||||||
@@ -1184,6 +1220,18 @@ const docTemplate = `{
|
|||||||
"name": "page",
|
"name": "page",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "PaidAtFrom filters orders by paid_at \u003e= this time.",
|
||||||
|
"name": "paid_at_from",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "PaidAtTo filters orders by paid_at \u003c= this time.",
|
||||||
|
"name": "paid_at_to",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"enum": [
|
"enum": [
|
||||||
"created",
|
"created",
|
||||||
|
|||||||
@@ -617,6 +617,24 @@
|
|||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "AmountPaidMax filters orders by amount_paid \u003c= this amount (cents).",
|
||||||
|
"name": "amount_paid_max",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "AmountPaidMin filters orders by amount_paid \u003e= this amount (cents).",
|
||||||
|
"name": "amount_paid_min",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "ContentID filters orders by purchased content id (via order_items join).",
|
||||||
|
"name": "content_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).",
|
"description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).",
|
||||||
@@ -629,6 +647,18 @@
|
|||||||
"name": "page",
|
"name": "page",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "PaidAtFrom filters orders by paid_at \u003e= this time.",
|
||||||
|
"name": "paid_at_from",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "PaidAtTo filters orders by paid_at \u003c= this time.",
|
||||||
|
"name": "paid_at_to",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"enum": [
|
"enum": [
|
||||||
"created",
|
"created",
|
||||||
@@ -1166,6 +1196,12 @@
|
|||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "ContentID filters orders by purchased content id (via order_items join).",
|
||||||
|
"name": "content_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).",
|
"description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).",
|
||||||
@@ -1178,6 +1214,18 @@
|
|||||||
"name": "page",
|
"name": "page",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "PaidAtFrom filters orders by paid_at \u003e= this time.",
|
||||||
|
"name": "paid_at_from",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "PaidAtTo filters orders by paid_at \u003c= this time.",
|
||||||
|
"name": "paid_at_to",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"enum": [
|
"enum": [
|
||||||
"created",
|
"created",
|
||||||
|
|||||||
@@ -1386,6 +1386,19 @@ paths:
|
|||||||
name: tenantCode
|
name: tenantCode
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- description: AmountPaidMax filters orders by amount_paid <= this amount (cents).
|
||||||
|
in: query
|
||||||
|
name: amount_paid_max
|
||||||
|
type: integer
|
||||||
|
- description: AmountPaidMin filters orders by amount_paid >= this amount (cents).
|
||||||
|
in: query
|
||||||
|
name: amount_paid_min
|
||||||
|
type: integer
|
||||||
|
- description: ContentID filters orders by purchased content id (via order_items
|
||||||
|
join).
|
||||||
|
in: query
|
||||||
|
name: content_id
|
||||||
|
type: integer
|
||||||
- description: Limit is page size; only values in {10,20,50,100} are accepted
|
- description: Limit is page size; only values in {10,20,50,100} are accepted
|
||||||
(otherwise defaults to 10).
|
(otherwise defaults to 10).
|
||||||
in: query
|
in: query
|
||||||
@@ -1395,6 +1408,14 @@ paths:
|
|||||||
in: query
|
in: query
|
||||||
name: page
|
name: page
|
||||||
type: integer
|
type: integer
|
||||||
|
- description: PaidAtFrom filters orders by paid_at >= this time.
|
||||||
|
in: query
|
||||||
|
name: paid_at_from
|
||||||
|
type: string
|
||||||
|
- description: PaidAtTo filters orders by paid_at <= this time.
|
||||||
|
in: query
|
||||||
|
name: paid_at_to
|
||||||
|
type: string
|
||||||
- description: Status filters orders by order status.
|
- description: Status filters orders by order status.
|
||||||
enum:
|
enum:
|
||||||
- created
|
- created
|
||||||
@@ -1745,6 +1766,11 @@ paths:
|
|||||||
name: tenantCode
|
name: tenantCode
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- description: ContentID filters orders by purchased content id (via order_items
|
||||||
|
join).
|
||||||
|
in: query
|
||||||
|
name: content_id
|
||||||
|
type: integer
|
||||||
- description: Limit is page size; only values in {10,20,50,100} are accepted
|
- description: Limit is page size; only values in {10,20,50,100} are accepted
|
||||||
(otherwise defaults to 10).
|
(otherwise defaults to 10).
|
||||||
in: query
|
in: query
|
||||||
@@ -1754,6 +1780,14 @@ paths:
|
|||||||
in: query
|
in: query
|
||||||
name: page
|
name: page
|
||||||
type: integer
|
type: integer
|
||||||
|
- description: PaidAtFrom filters orders by paid_at >= this time.
|
||||||
|
in: query
|
||||||
|
name: paid_at_from
|
||||||
|
type: string
|
||||||
|
- description: PaidAtTo filters orders by paid_at <= this time.
|
||||||
|
in: query
|
||||||
|
name: paid_at_to
|
||||||
|
type: string
|
||||||
- description: Status filters orders by order status.
|
- description: Status filters orders by order status.
|
||||||
enum:
|
enum:
|
||||||
- created
|
- created
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ GET {{ host }}/t/{{ tenantCode }}/v1/orders?page=1&limit=10
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
Authorization: Bearer {{ token }}
|
Authorization: Bearer {{ token }}
|
||||||
|
|
||||||
|
### Tenant - My orders list (filter by status/content_id/paid_at window)
|
||||||
|
GET {{ host }}/t/{{ tenantCode }}/v1/orders?page=1&limit=10&status=paid&content_id={{ contentID }}
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer {{ token }}
|
||||||
|
|
||||||
### Tenant - My order detail
|
### Tenant - My order detail
|
||||||
GET {{ host }}/t/{{ tenantCode }}/v1/orders/{{ orderID }}
|
GET {{ host }}/t/{{ tenantCode }}/v1/orders/{{ orderID }}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
@@ -113,6 +118,11 @@ GET {{ host }}/t/{{ tenantCode }}/v1/admin/orders?page=1&limit=10
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
Authorization: Bearer {{ token }}
|
Authorization: Bearer {{ token }}
|
||||||
|
|
||||||
|
### Tenant Admin - Orders list (filter by user_id/content_id/paid_at range/amount_paid range)
|
||||||
|
GET {{ host }}/t/{{ tenantCode }}/v1/admin/orders?page=1&limit=10&user_id=2&content_id={{ contentID }}&paid_at_from=2025-01-01T00:00:00Z&paid_at_to=2026-01-01T00:00:00Z&amount_paid_min=1&amount_paid_max=99999999
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer {{ token }}
|
||||||
|
|
||||||
### Tenant Admin - Order detail
|
### Tenant Admin - Order detail
|
||||||
@orderID = 1
|
@orderID = 1
|
||||||
GET {{ host }}/t/{{ tenantCode }}/v1/admin/orders/{{ orderID }}
|
GET {{ host }}/t/{{ tenantCode }}/v1/admin/orders/{{ orderID }}
|
||||||
|
|||||||
Reference in New Issue
Block a user