feat: 实现套餐管理模块,包含套餐系列、双状态管理、废弃模型清理
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m24s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m24s
- 新增套餐系列管理 (CRUD + 状态切换) - 新增套餐管理 (CRUD + 启用/禁用 + 上架/下架双状态) - 清理 8 个废弃分佣模型及对应数据库表 - Package 模型新增建议成本价、建议售价、上架状态字段 - 完整的 Store/Service/Handler 三层实现 - 包含单元测试和集成测试 - 归档 add-package-module change - 新增多个 OpenSpec changes (订单支付、店铺套餐分配、一次性分佣、卡设备系列绑定)
This commit is contained in:
@@ -34,5 +34,7 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers {
|
||||
AssetAllocationRecord: admin.NewAssetAllocationRecordHandler(svc.AssetAllocationRecord),
|
||||
Storage: admin.NewStorageHandler(deps.StorageService),
|
||||
Carrier: admin.NewCarrierHandler(svc.Carrier),
|
||||
PackageSeries: admin.NewPackageSeriesHandler(svc.PackageSeries),
|
||||
Package: admin.NewPackageHandler(svc.Package),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
iotCardSvc "github.com/break/junhong_cmp_fiber/internal/service/iot_card"
|
||||
iotCardImportSvc "github.com/break/junhong_cmp_fiber/internal/service/iot_card_import"
|
||||
myCommissionSvc "github.com/break/junhong_cmp_fiber/internal/service/my_commission"
|
||||
packageSvc "github.com/break/junhong_cmp_fiber/internal/service/package"
|
||||
packageSeriesSvc "github.com/break/junhong_cmp_fiber/internal/service/package_series"
|
||||
permissionSvc "github.com/break/junhong_cmp_fiber/internal/service/permission"
|
||||
personalCustomerSvc "github.com/break/junhong_cmp_fiber/internal/service/personal_customer"
|
||||
roleSvc "github.com/break/junhong_cmp_fiber/internal/service/role"
|
||||
@@ -45,6 +47,8 @@ type services struct {
|
||||
DeviceImport *deviceImportSvc.Service
|
||||
AssetAllocationRecord *assetAllocationRecordSvc.Service
|
||||
Carrier *carrierSvc.Service
|
||||
PackageSeries *packageSeriesSvc.Service
|
||||
Package *packageSvc.Service
|
||||
}
|
||||
|
||||
func initServices(s *stores, deps *Dependencies) *services {
|
||||
@@ -70,5 +74,7 @@ func initServices(s *stores, deps *Dependencies) *services {
|
||||
DeviceImport: deviceImportSvc.New(deps.DB, s.DeviceImportTask, deps.QueueClient),
|
||||
AssetAllocationRecord: assetAllocationRecordSvc.New(deps.DB, s.AssetAllocationRecord, s.Shop, s.Account),
|
||||
Carrier: carrierSvc.New(s.Carrier),
|
||||
PackageSeries: packageSeriesSvc.New(s.PackageSeries),
|
||||
Package: packageSvc.New(s.Package, s.PackageSeries),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ type stores struct {
|
||||
DeviceImportTask *postgres.DeviceImportTaskStore
|
||||
AssetAllocationRecord *postgres.AssetAllocationRecordStore
|
||||
Carrier *postgres.CarrierStore
|
||||
PackageSeries *postgres.PackageSeriesStore
|
||||
Package *postgres.PackageStore
|
||||
}
|
||||
|
||||
func initStores(deps *Dependencies) *stores {
|
||||
@@ -53,5 +55,7 @@ func initStores(deps *Dependencies) *stores {
|
||||
DeviceImportTask: postgres.NewDeviceImportTaskStore(deps.DB, deps.Redis),
|
||||
AssetAllocationRecord: postgres.NewAssetAllocationRecordStore(deps.DB, deps.Redis),
|
||||
Carrier: postgres.NewCarrierStore(deps.DB),
|
||||
PackageSeries: postgres.NewPackageSeriesStore(deps.DB),
|
||||
Package: postgres.NewPackageStore(deps.DB),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ type Handlers struct {
|
||||
AssetAllocationRecord *admin.AssetAllocationRecordHandler
|
||||
Storage *admin.StorageHandler
|
||||
Carrier *admin.CarrierHandler
|
||||
PackageSeries *admin.PackageSeriesHandler
|
||||
Package *admin.PackageHandler
|
||||
}
|
||||
|
||||
// Middlewares 封装所有中间件
|
||||
|
||||
130
internal/handler/admin/package.go
Normal file
130
internal/handler/admin/package.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
packageService "github.com/break/junhong_cmp_fiber/internal/service/package"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
)
|
||||
|
||||
type PackageHandler struct {
|
||||
service *packageService.Service
|
||||
}
|
||||
|
||||
func NewPackageHandler(service *packageService.Service) *PackageHandler {
|
||||
return &PackageHandler{service: service}
|
||||
}
|
||||
|
||||
func (h *PackageHandler) List(c *fiber.Ctx) error {
|
||||
var req dto.PackageListRequest
|
||||
if err := c.QueryParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
packages, total, err := h.service.List(c.UserContext(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.SuccessWithPagination(c, packages, total, req.Page, req.PageSize)
|
||||
}
|
||||
|
||||
func (h *PackageHandler) Create(c *fiber.Ctx) error {
|
||||
var req dto.CreatePackageRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
pkg, err := h.service.Create(c.UserContext(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, pkg)
|
||||
}
|
||||
|
||||
func (h *PackageHandler) Get(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的套餐 ID")
|
||||
}
|
||||
|
||||
pkg, err := h.service.Get(c.UserContext(), uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, pkg)
|
||||
}
|
||||
|
||||
func (h *PackageHandler) Update(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的套餐 ID")
|
||||
}
|
||||
|
||||
var req dto.UpdatePackageRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
pkg, err := h.service.Update(c.UserContext(), uint(id), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, pkg)
|
||||
}
|
||||
|
||||
func (h *PackageHandler) Delete(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的套餐 ID")
|
||||
}
|
||||
|
||||
if err := h.service.Delete(c.UserContext(), uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
func (h *PackageHandler) UpdateStatus(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的套餐 ID")
|
||||
}
|
||||
|
||||
var req dto.UpdatePackageStatusRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
if err := h.service.UpdateStatus(c.UserContext(), uint(id), req.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
func (h *PackageHandler) UpdateShelfStatus(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的套餐 ID")
|
||||
}
|
||||
|
||||
var req dto.UpdatePackageShelfStatusRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
if err := h.service.UpdateShelfStatus(c.UserContext(), uint(id), req.ShelfStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
112
internal/handler/admin/package_series.go
Normal file
112
internal/handler/admin/package_series.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
packageSeriesService "github.com/break/junhong_cmp_fiber/internal/service/package_series"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
)
|
||||
|
||||
type PackageSeriesHandler struct {
|
||||
service *packageSeriesService.Service
|
||||
}
|
||||
|
||||
func NewPackageSeriesHandler(service *packageSeriesService.Service) *PackageSeriesHandler {
|
||||
return &PackageSeriesHandler{service: service}
|
||||
}
|
||||
|
||||
func (h *PackageSeriesHandler) List(c *fiber.Ctx) error {
|
||||
var req dto.PackageSeriesListRequest
|
||||
if err := c.QueryParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
seriesList, total, err := h.service.List(c.UserContext(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.SuccessWithPagination(c, seriesList, total, req.Page, req.PageSize)
|
||||
}
|
||||
|
||||
func (h *PackageSeriesHandler) Create(c *fiber.Ctx) error {
|
||||
var req dto.CreatePackageSeriesRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
series, err := h.service.Create(c.UserContext(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, series)
|
||||
}
|
||||
|
||||
func (h *PackageSeriesHandler) Get(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的套餐系列 ID")
|
||||
}
|
||||
|
||||
series, err := h.service.Get(c.UserContext(), uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, series)
|
||||
}
|
||||
|
||||
func (h *PackageSeriesHandler) Update(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的套餐系列 ID")
|
||||
}
|
||||
|
||||
var req dto.UpdatePackageSeriesRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
series, err := h.service.Update(c.UserContext(), uint(id), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, series)
|
||||
}
|
||||
|
||||
func (h *PackageSeriesHandler) Delete(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的套餐系列 ID")
|
||||
}
|
||||
|
||||
if err := h.service.Delete(c.UserContext(), uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
func (h *PackageSeriesHandler) UpdateStatus(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的套餐系列 ID")
|
||||
}
|
||||
|
||||
var req dto.UpdatePackageSeriesStatusRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
if err := h.service.UpdateStatus(c.UserContext(), uint(id), req.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
@@ -6,85 +6,6 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AgentHierarchy 代理层级关系模型
|
||||
// 树形代理关系(每个代理只有一个上级)
|
||||
type AgentHierarchy struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
AgentID uint `gorm:"column:agent_id;uniqueIndex:idx_agent_hierarchy_agent,where:deleted_at IS NULL;not null;comment:代理用户ID" json:"agent_id"`
|
||||
ParentAgentID uint `gorm:"column:parent_agent_id;index;comment:上级代理用户ID(NULL表示顶级代理)" json:"parent_agent_id"`
|
||||
Level int `gorm:"column:level;type:int;not null;comment:代理层级(1, 2, 3...)" json:"level"`
|
||||
Path string `gorm:"column:path;type:varchar(500);comment:代理路径(如: 1/5/12)" json:"path"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (AgentHierarchy) TableName() string {
|
||||
return "tb_agent_hierarchy"
|
||||
}
|
||||
|
||||
// CommissionRule 分佣规则模型
|
||||
// 三种分佣类型:一次性/长期/组合
|
||||
type CommissionRule struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
AgentID uint `gorm:"column:agent_id;index;not null;comment:代理用户ID" json:"agent_id"`
|
||||
BusinessType string `gorm:"column:business_type;type:varchar(50);not null;comment:业务类型" json:"business_type"`
|
||||
CardType string `gorm:"column:card_type;type:varchar(50);not null;comment:卡类型 number_card-号卡 iot_card-IoT卡" json:"card_type"`
|
||||
SeriesID uint `gorm:"column:series_id;index;comment:套餐系列ID(一次性分佣时用)" json:"series_id"`
|
||||
PackageID uint `gorm:"column:package_id;index;comment:套餐ID(长期分佣时用)" json:"package_id"`
|
||||
CommissionType string `gorm:"column:commission_type;type:varchar(50);not null;comment:分佣类型 one_time-一次性 long_term-长期 combined-组合" json:"commission_type"`
|
||||
CommissionMode string `gorm:"column:commission_mode;type:varchar(20);not null;comment:分佣模式 fixed-固定金额 percent-百分比" json:"commission_mode"`
|
||||
CommissionValue int64 `gorm:"column:commission_value;type:bigint;not null;comment:分佣值(分为单位,百分比时为千分比如2000表示20%)" json:"commission_value"`
|
||||
UnfreezeDays int `gorm:"column:unfreeze_days;type:int;default:0;comment:解冻天数" json:"unfreeze_days"`
|
||||
MinActivationForUnfreeze int `gorm:"column:min_activation_for_unfreeze;type:int;default:0;comment:解冻最小激活量" json:"min_activation_for_unfreeze"`
|
||||
ApprovalType string `gorm:"column:approval_type;type:varchar(20);default:'auto';comment:审批类型 auto-自动 manual-人工" json:"approval_type"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-启用 2-禁用" json:"status"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (CommissionRule) TableName() string {
|
||||
return "tb_commission_rule"
|
||||
}
|
||||
|
||||
// CommissionLadder 阶梯分佣配置模型
|
||||
// 支持按激活量、提货量、充值量设置阶梯佣金
|
||||
type CommissionLadder struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
RuleID uint `gorm:"column:rule_id;index;not null;comment:分佣规则ID" json:"rule_id"`
|
||||
LadderType string `gorm:"column:ladder_type;type:varchar(50);not null;comment:阶梯类型 activation-激活量 pickup-提货量 deposit-充值量" json:"ladder_type"`
|
||||
ThresholdValue int `gorm:"column:threshold_value;type:int;not null;comment:阈值" json:"threshold_value"`
|
||||
CommissionMode string `gorm:"column:commission_mode;type:varchar(20);not null;comment:分佣模式 fixed-固定金额 percent-百分比" json:"commission_mode"`
|
||||
CommissionValue int64 `gorm:"column:commission_value;type:bigint;not null;comment:分佣值(分为单位,百分比时为千分比如2000表示20%)" json:"commission_value"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (CommissionLadder) TableName() string {
|
||||
return "tb_commission_ladder"
|
||||
}
|
||||
|
||||
// CommissionCombinedCondition 组合分佣条件模型
|
||||
// 支持时间点 OR 套餐周期阈值的 OR 条件解冻
|
||||
type CommissionCombinedCondition struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
RuleID uint `gorm:"column:rule_id;uniqueIndex:idx_commission_combined_rule,where:deleted_at IS NULL;not null;comment:分佣规则ID" json:"rule_id"`
|
||||
OneTimeCommissionMode string `gorm:"column:one_time_commission_mode;type:varchar(20);comment:一次性分佣模式 fixed-固定金额 percent-百分比" json:"one_time_commission_mode"`
|
||||
OneTimeCommissionValue int64 `gorm:"column:one_time_commission_value;type:bigint;comment:一次性分佣值(分为单位,百分比时为千分比如2000表示20%)" json:"one_time_commission_value"`
|
||||
LongTermCommissionMode string `gorm:"column:long_term_commission_mode;type:varchar(20);comment:长期分佣模式 fixed-固定金额 percent-百分比" json:"long_term_commission_mode"`
|
||||
LongTermCommissionValue int64 `gorm:"column:long_term_commission_value;type:bigint;comment:长期分佣值(分为单位,百分比时为千分比如2000表示20%)" json:"long_term_commission_value"`
|
||||
LongTermTriggerTimePoint *time.Time `gorm:"column:long_term_trigger_time_point;comment:长期分佣触发时间点(如实名后3个月)" json:"long_term_trigger_time_point"`
|
||||
LongTermTriggerPackageCycles int `gorm:"column:long_term_trigger_package_cycles;type:int;comment:长期分佣触发套餐周期数(如10个套餐周期)" json:"long_term_trigger_package_cycles"`
|
||||
LongTermTriggerNetworkMonths int `gorm:"column:long_term_trigger_network_months;type:int;comment:长期分佣触发在网月数(号卡专用)" json:"long_term_trigger_network_months"`
|
||||
LongTermUnfreezeDays int `gorm:"column:long_term_unfreeze_days;type:int;default:0;comment:长期分佣解冻天数" json:"long_term_unfreeze_days"`
|
||||
LongTermMinActivation int `gorm:"column:long_term_min_activation;type:int;default:0;comment:长期分佣解冻最小激活量" json:"long_term_min_activation"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (CommissionCombinedCondition) TableName() string {
|
||||
return "tb_commission_combined_condition"
|
||||
}
|
||||
|
||||
// CommissionRecord 分佣记录模型
|
||||
// 记录分佣的冻结、解冻、发放状态
|
||||
type CommissionRecord struct {
|
||||
@@ -106,57 +27,3 @@ type CommissionRecord struct {
|
||||
func (CommissionRecord) TableName() string {
|
||||
return "tb_commission_record"
|
||||
}
|
||||
|
||||
// CommissionApproval 分佣审批模型
|
||||
// 分佣解冻审批流程
|
||||
type CommissionApproval struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
CommissionRecordID uint `gorm:"column:commission_record_id;index;not null;comment:分佣记录ID" json:"commission_record_id"`
|
||||
ApproverID uint `gorm:"column:approver_id;index;comment:审批人用户ID" json:"approver_id"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-待审批 2-已通过 3-已拒绝" json:"status"`
|
||||
Reason string `gorm:"column:reason;type:text;comment:原因" json:"reason"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (CommissionApproval) TableName() string {
|
||||
return "tb_commission_approval"
|
||||
}
|
||||
|
||||
// CommissionTemplate 分佣模板模型
|
||||
// 创建和管理分佣模板,快速为代理分配产品时设置佣金规则
|
||||
type CommissionTemplate struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
TemplateName string `gorm:"column:template_name;type:varchar(255);uniqueIndex:idx_commission_template_name,where:deleted_at IS NULL;not null;comment:模板名称" json:"template_name"`
|
||||
BusinessType string `gorm:"column:business_type;type:varchar(50);not null;comment:业务类型" json:"business_type"`
|
||||
CardType string `gorm:"column:card_type;type:varchar(50);not null;comment:卡类型 number_card-号卡 iot_card-IoT卡" json:"card_type"`
|
||||
CommissionType string `gorm:"column:commission_type;type:varchar(50);not null;comment:分佣类型 one_time-一次性 long_term-长期 combined-组合" json:"commission_type"`
|
||||
CommissionMode string `gorm:"column:commission_mode;type:varchar(20);not null;comment:分佣模式 fixed-固定金额 percent-百分比" json:"commission_mode"`
|
||||
CommissionValue int64 `gorm:"column:commission_value;type:bigint;not null;comment:分佣值(分为单位,百分比时为千分比如2000表示20%)" json:"commission_value"`
|
||||
UnfreezeDays int `gorm:"column:unfreeze_days;type:int;default:0;comment:解冻天数" json:"unfreeze_days"`
|
||||
MinActivationForUnfreeze int `gorm:"column:min_activation_for_unfreeze;type:int;default:0;comment:解冻最小激活量" json:"min_activation_for_unfreeze"`
|
||||
ApprovalType string `gorm:"column:approval_type;type:varchar(20);default:'auto';comment:审批类型 auto-自动 manual-人工" json:"approval_type"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (CommissionTemplate) TableName() string {
|
||||
return "tb_commission_template"
|
||||
}
|
||||
|
||||
// CarrierSettlement 号卡运营商结算模型
|
||||
// 运营商周期性结算的佣金总额,再分配给代理
|
||||
type CarrierSettlement struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
CommissionRecordID uint `gorm:"column:commission_record_id;uniqueIndex:idx_carrier_settlement_record,where:deleted_at IS NULL;not null;comment:分佣记录ID" json:"commission_record_id"`
|
||||
AgentID uint `gorm:"column:agent_id;index;not null;comment:代理用户ID" json:"agent_id"`
|
||||
SettlementMonth string `gorm:"column:settlement_month;type:varchar(20);not null;comment:结算月份(如 2026-01)" json:"settlement_month"`
|
||||
SettlementAmount int64 `gorm:"column:settlement_amount;type:bigint;not null;comment:结算金额(分为单位)" json:"settlement_amount"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-待结算 2-已结算" json:"status"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (CarrierSettlement) TableName() string {
|
||||
return "tb_carrier_settlement"
|
||||
}
|
||||
|
||||
101
internal/model/dto/package_dto.go
Normal file
101
internal/model/dto/package_dto.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package dto
|
||||
|
||||
// CreatePackageRequest 创建套餐请求
|
||||
type CreatePackageRequest struct {
|
||||
PackageCode string `json:"package_code" validate:"required,min=1,max=100" required:"true" minLength:"1" maxLength:"100" description:"套餐编码"`
|
||||
PackageName string `json:"package_name" validate:"required,min=1,max=255" required:"true" minLength:"1" maxLength:"255" description:"套餐名称"`
|
||||
SeriesID *uint `json:"series_id" validate:"omitempty" description:"套餐系列ID"`
|
||||
PackageType string `json:"package_type" validate:"required,oneof=formal addon" required:"true" description:"套餐类型 (formal:正式套餐, addon:附加套餐)"`
|
||||
DurationMonths int `json:"duration_months" validate:"required,min=1,max=120" required:"true" minimum:"1" maximum:"120" description:"套餐时长(月数)"`
|
||||
DataType *string `json:"data_type" validate:"omitempty,oneof=real virtual" description:"流量类型 (real:真流量, virtual:虚流量)"`
|
||||
RealDataMB *int64 `json:"real_data_mb" validate:"omitempty,min=0" minimum:"0" description:"真流量额度(MB)"`
|
||||
VirtualDataMB *int64 `json:"virtual_data_mb" validate:"omitempty,min=0" minimum:"0" description:"虚流量额度(MB)"`
|
||||
DataAmountMB *int64 `json:"data_amount_mb" validate:"omitempty,min=0" minimum:"0" description:"总流量额度(MB)"`
|
||||
Price int64 `json:"price" validate:"required,min=0" required:"true" minimum:"0" description:"套餐价格(分)"`
|
||||
SuggestedCostPrice *int64 `json:"suggested_cost_price" validate:"omitempty,min=0" minimum:"0" description:"建议成本价(分)"`
|
||||
SuggestedRetailPrice *int64 `json:"suggested_retail_price" validate:"omitempty,min=0" minimum:"0" description:"建议售价(分)"`
|
||||
}
|
||||
|
||||
// UpdatePackageRequest 更新套餐请求
|
||||
type UpdatePackageRequest struct {
|
||||
PackageName *string `json:"package_name" validate:"omitempty,min=1,max=255" minLength:"1" maxLength:"255" description:"套餐名称"`
|
||||
SeriesID *uint `json:"series_id" validate:"omitempty" description:"套餐系列ID"`
|
||||
PackageType *string `json:"package_type" validate:"omitempty,oneof=formal addon" description:"套餐类型 (formal:正式套餐, addon:附加套餐)"`
|
||||
DurationMonths *int `json:"duration_months" validate:"omitempty,min=1,max=120" minimum:"1" maximum:"120" description:"套餐时长(月数)"`
|
||||
DataType *string `json:"data_type" validate:"omitempty,oneof=real virtual" description:"流量类型 (real:真流量, virtual:虚流量)"`
|
||||
RealDataMB *int64 `json:"real_data_mb" validate:"omitempty,min=0" minimum:"0" description:"真流量额度(MB)"`
|
||||
VirtualDataMB *int64 `json:"virtual_data_mb" validate:"omitempty,min=0" minimum:"0" description:"虚流量额度(MB)"`
|
||||
DataAmountMB *int64 `json:"data_amount_mb" validate:"omitempty,min=0" minimum:"0" description:"总流量额度(MB)"`
|
||||
Price *int64 `json:"price" validate:"omitempty,min=0" minimum:"0" description:"套餐价格(分)"`
|
||||
SuggestedCostPrice *int64 `json:"suggested_cost_price" validate:"omitempty,min=0" minimum:"0" description:"建议成本价(分)"`
|
||||
SuggestedRetailPrice *int64 `json:"suggested_retail_price" validate:"omitempty,min=0" minimum:"0" description:"建议售价(分)"`
|
||||
}
|
||||
|
||||
// PackageListRequest 套餐列表请求
|
||||
type PackageListRequest struct {
|
||||
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
|
||||
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
|
||||
PackageName *string `json:"package_name" query:"package_name" validate:"omitempty,max=255" maxLength:"255" description:"套餐名称(模糊搜索)"`
|
||||
SeriesID *uint `json:"series_id" query:"series_id" validate:"omitempty" description:"套餐系列ID"`
|
||||
Status *int `json:"status" query:"status" validate:"omitempty,oneof=1 2" description:"状态 (1:启用, 2:禁用)"`
|
||||
ShelfStatus *int `json:"shelf_status" query:"shelf_status" validate:"omitempty,oneof=1 2" description:"上架状态 (1:上架, 2:下架)"`
|
||||
PackageType *string `json:"package_type" query:"package_type" validate:"omitempty,oneof=formal addon" description:"套餐类型 (formal:正式套餐, addon:附加套餐)"`
|
||||
}
|
||||
|
||||
// UpdatePackageStatusRequest 更新套餐状态请求
|
||||
type UpdatePackageStatusRequest struct {
|
||||
Status int `json:"status" validate:"required,oneof=1 2" required:"true" description:"状态 (1:启用, 2:禁用)"`
|
||||
}
|
||||
|
||||
// UpdatePackageShelfStatusRequest 更新套餐上架状态请求
|
||||
type UpdatePackageShelfStatusRequest struct {
|
||||
ShelfStatus int `json:"shelf_status" validate:"required,oneof=1 2" required:"true" description:"上架状态 (1:上架, 2:下架)"`
|
||||
}
|
||||
|
||||
// PackageResponse 套餐响应
|
||||
type PackageResponse struct {
|
||||
ID uint `json:"id" description:"套餐ID"`
|
||||
PackageCode string `json:"package_code" description:"套餐编码"`
|
||||
PackageName string `json:"package_name" description:"套餐名称"`
|
||||
SeriesID *uint `json:"series_id" description:"套餐系列ID"`
|
||||
PackageType string `json:"package_type" description:"套餐类型 (formal:正式套餐, addon:附加套餐)"`
|
||||
DurationMonths int `json:"duration_months" description:"套餐时长(月数)"`
|
||||
DataType string `json:"data_type" description:"流量类型 (real:真流量, virtual:虚流量)"`
|
||||
RealDataMB int64 `json:"real_data_mb" description:"真流量额度(MB)"`
|
||||
VirtualDataMB int64 `json:"virtual_data_mb" description:"虚流量额度(MB)"`
|
||||
DataAmountMB int64 `json:"data_amount_mb" description:"总流量额度(MB)"`
|
||||
Price int64 `json:"price" description:"套餐价格(分)"`
|
||||
SuggestedCostPrice int64 `json:"suggested_cost_price" description:"建议成本价(分)"`
|
||||
SuggestedRetailPrice int64 `json:"suggested_retail_price" description:"建议售价(分)"`
|
||||
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
|
||||
ShelfStatus int `json:"shelf_status" description:"上架状态 (1:上架, 2:下架)"`
|
||||
CreatedAt string `json:"created_at" description:"创建时间"`
|
||||
UpdatedAt string `json:"updated_at" description:"更新时间"`
|
||||
}
|
||||
|
||||
// UpdatePackageParams 更新套餐聚合参数
|
||||
type UpdatePackageParams struct {
|
||||
IDReq
|
||||
UpdatePackageRequest
|
||||
}
|
||||
|
||||
// UpdatePackageStatusParams 更新套餐状态聚合参数
|
||||
type UpdatePackageStatusParams struct {
|
||||
IDReq
|
||||
UpdatePackageStatusRequest
|
||||
}
|
||||
|
||||
// UpdatePackageShelfStatusParams 更新套餐上架状态聚合参数
|
||||
type UpdatePackageShelfStatusParams struct {
|
||||
IDReq
|
||||
UpdatePackageShelfStatusRequest
|
||||
}
|
||||
|
||||
// PackagePageResult 套餐分页结果
|
||||
type PackagePageResult struct {
|
||||
List []*PackageResponse `json:"list" description:"套餐列表"`
|
||||
Total int64 `json:"total" description:"总数"`
|
||||
Page int `json:"page" description:"当前页"`
|
||||
PageSize int `json:"page_size" description:"每页数量"`
|
||||
TotalPages int `json:"total_pages" description:"总页数"`
|
||||
}
|
||||
59
internal/model/dto/package_series_dto.go
Normal file
59
internal/model/dto/package_series_dto.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package dto
|
||||
|
||||
// CreatePackageSeriesRequest 创建套餐系列请求
|
||||
type CreatePackageSeriesRequest struct {
|
||||
SeriesCode string `json:"series_code" validate:"required,min=1,max=100" required:"true" minLength:"1" maxLength:"100" description:"系列编码"`
|
||||
SeriesName string `json:"series_name" validate:"required,min=1,max=255" required:"true" minLength:"1" maxLength:"255" description:"系列名称"`
|
||||
Description string `json:"description" validate:"omitempty,max=500" maxLength:"500" description:"描述"`
|
||||
}
|
||||
|
||||
// UpdatePackageSeriesRequest 更新套餐系列请求
|
||||
type UpdatePackageSeriesRequest struct {
|
||||
SeriesName *string `json:"series_name" validate:"omitempty,min=1,max=255" minLength:"1" maxLength:"255" description:"系列名称"`
|
||||
Description *string `json:"description" validate:"omitempty,max=500" maxLength:"500" description:"描述"`
|
||||
}
|
||||
|
||||
// PackageSeriesListRequest 套餐系列列表请求
|
||||
type PackageSeriesListRequest struct {
|
||||
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
|
||||
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
|
||||
SeriesName *string `json:"series_name" query:"series_name" validate:"omitempty,max=255" maxLength:"255" description:"系列名称(模糊搜索)"`
|
||||
Status *int `json:"status" query:"status" validate:"omitempty,oneof=1 2" description:"状态 (1:启用, 2:禁用)"`
|
||||
}
|
||||
|
||||
// UpdatePackageSeriesStatusRequest 更新套餐系列状态请求
|
||||
type UpdatePackageSeriesStatusRequest struct {
|
||||
Status int `json:"status" validate:"required,oneof=1 2" required:"true" description:"状态 (1:启用, 2:禁用)"`
|
||||
}
|
||||
|
||||
// PackageSeriesResponse 套餐系列响应
|
||||
type PackageSeriesResponse struct {
|
||||
ID uint `json:"id" description:"系列ID"`
|
||||
SeriesCode string `json:"series_code" description:"系列编码"`
|
||||
SeriesName string `json:"series_name" description:"系列名称"`
|
||||
Description string `json:"description" description:"描述"`
|
||||
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
|
||||
CreatedAt string `json:"created_at" description:"创建时间"`
|
||||
UpdatedAt string `json:"updated_at" description:"更新时间"`
|
||||
}
|
||||
|
||||
// UpdatePackageSeriesParams 更新套餐系列聚合参数
|
||||
type UpdatePackageSeriesParams struct {
|
||||
IDReq
|
||||
UpdatePackageSeriesRequest
|
||||
}
|
||||
|
||||
// UpdatePackageSeriesStatusParams 更新套餐系列状态聚合参数
|
||||
type UpdatePackageSeriesStatusParams struct {
|
||||
IDReq
|
||||
UpdatePackageSeriesStatusRequest
|
||||
}
|
||||
|
||||
// PackageSeriesPageResult 套餐系列分页结果
|
||||
type PackageSeriesPageResult struct {
|
||||
List []*PackageSeriesResponse `json:"list" description:"套餐系列列表"`
|
||||
Total int64 `json:"total" description:"总数"`
|
||||
Page int `json:"page" description:"当前页"`
|
||||
PageSize int `json:"page_size" description:"每页数量"`
|
||||
TotalPages int `json:"total_pages" description:"总页数"`
|
||||
}
|
||||
@@ -26,18 +26,21 @@ func (PackageSeries) TableName() string {
|
||||
// 只适用于 IoT 卡,支持真流量/虚流量共存机制
|
||||
type Package struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
PackageCode string `gorm:"column:package_code;type:varchar(100);uniqueIndex:idx_package_code,where:deleted_at IS NULL;not null;comment:套餐编码" json:"package_code"`
|
||||
PackageName string `gorm:"column:package_name;type:varchar(255);not null;comment:套餐名称" json:"package_name"`
|
||||
SeriesID uint `gorm:"column:series_id;index;comment:套餐系列ID" json:"series_id"`
|
||||
PackageType string `gorm:"column:package_type;type:varchar(50);not null;comment:套餐类型 formal-正式套餐 addon-附加套餐" json:"package_type"`
|
||||
DurationMonths int `gorm:"column:duration_months;type:int;not null;comment:套餐时长(月数) 1-月套餐 12-年套餐" json:"duration_months"`
|
||||
DataType string `gorm:"column:data_type;type:varchar(20);comment:流量类型 real-真流量 virtual-虚流量" json:"data_type"`
|
||||
RealDataMB int64 `gorm:"column:real_data_mb;type:bigint;default:0;comment:真流量额度(MB)" json:"real_data_mb"`
|
||||
VirtualDataMB int64 `gorm:"column:virtual_data_mb;type:bigint;default:0;comment:虚流量额度(MB,用于停机判断)" json:"virtual_data_mb"`
|
||||
DataAmountMB int64 `gorm:"column:data_amount_mb;type:bigint;default:0;comment:总流量额度(MB)" json:"data_amount_mb"`
|
||||
Price int64 `gorm:"column:price;type:bigint;not null;comment:套餐价格(分为单位)" json:"price"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-启用 2-禁用" json:"status"`
|
||||
BaseModel `gorm:"embedded"`
|
||||
PackageCode string `gorm:"column:package_code;type:varchar(100);uniqueIndex:idx_package_code,where:deleted_at IS NULL;not null;comment:套餐编码" json:"package_code"`
|
||||
PackageName string `gorm:"column:package_name;type:varchar(255);not null;comment:套餐名称" json:"package_name"`
|
||||
SeriesID uint `gorm:"column:series_id;index;comment:套餐系列ID" json:"series_id"`
|
||||
PackageType string `gorm:"column:package_type;type:varchar(50);not null;comment:套餐类型 formal-正式套餐 addon-附加套餐" json:"package_type"`
|
||||
DurationMonths int `gorm:"column:duration_months;type:int;not null;comment:套餐时长(月数) 1-月套餐 12-年套餐" json:"duration_months"`
|
||||
DataType string `gorm:"column:data_type;type:varchar(20);comment:流量类型 real-真流量 virtual-虚流量" json:"data_type"`
|
||||
RealDataMB int64 `gorm:"column:real_data_mb;type:bigint;default:0;comment:真流量额度(MB)" json:"real_data_mb"`
|
||||
VirtualDataMB int64 `gorm:"column:virtual_data_mb;type:bigint;default:0;comment:虚流量额度(MB,用于停机判断)" json:"virtual_data_mb"`
|
||||
DataAmountMB int64 `gorm:"column:data_amount_mb;type:bigint;default:0;comment:总流量额度(MB)" json:"data_amount_mb"`
|
||||
Price int64 `gorm:"column:price;type:bigint;not null;comment:套餐价格(分为单位)" json:"price"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-启用 2-禁用" json:"status"`
|
||||
SuggestedCostPrice int64 `gorm:"column:suggested_cost_price;type:bigint;default:0;comment:建议成本价(分为单位)" json:"suggested_cost_price"`
|
||||
SuggestedRetailPrice int64 `gorm:"column:suggested_retail_price;type:bigint;default:0;comment:建议售价(分为单位)" json:"suggested_retail_price"`
|
||||
ShelfStatus int `gorm:"column:shelf_status;type:int;default:2;not null;comment:上架状态 1-上架 2-下架" json:"shelf_status"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
@@ -45,23 +48,6 @@ func (Package) TableName() string {
|
||||
return "tb_package"
|
||||
}
|
||||
|
||||
// AgentPackageAllocation 代理套餐分配模型
|
||||
// 为直属下级代理分配套餐,设置佣金模式
|
||||
type AgentPackageAllocation struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
AgentID uint `gorm:"column:agent_id;index;not null;comment:代理用户ID" json:"agent_id"`
|
||||
PackageID uint `gorm:"column:package_id;index;not null;comment:套餐ID" json:"package_id"`
|
||||
CostPrice int64 `gorm:"column:cost_price;type:bigint;not null;comment:成本价(分为单位)" json:"cost_price"`
|
||||
RetailPrice int64 `gorm:"column:retail_price;type:bigint;not null;comment:零售价(分为单位)" json:"retail_price"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-启用 2-禁用" json:"status"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (AgentPackageAllocation) TableName() string {
|
||||
return "tb_agent_package_allocation"
|
||||
}
|
||||
|
||||
// PackageUsage 套餐使用情况模型
|
||||
// 跟踪单卡套餐和设备级套餐的流量使用
|
||||
type PackageUsage struct {
|
||||
|
||||
@@ -70,6 +70,12 @@ func RegisterAdminRoutes(router fiber.Router, handlers *bootstrap.Handlers, midd
|
||||
if handlers.Carrier != nil {
|
||||
registerCarrierRoutes(authGroup, handlers.Carrier, doc, basePath)
|
||||
}
|
||||
if handlers.PackageSeries != nil {
|
||||
registerPackageSeriesRoutes(authGroup, handlers.PackageSeries, doc, basePath)
|
||||
}
|
||||
if handlers.Package != nil {
|
||||
registerPackageRoutes(authGroup, handlers.Package, doc, basePath)
|
||||
}
|
||||
}
|
||||
|
||||
func registerAdminAuthRoutes(router fiber.Router, handler interface{}, authMiddleware fiber.Handler, doc *openapi.Generator, basePath string) {
|
||||
|
||||
70
internal/routes/package.go
Normal file
70
internal/routes/package.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/handler/admin"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/openapi"
|
||||
)
|
||||
|
||||
func registerPackageRoutes(router fiber.Router, handler *admin.PackageHandler, doc *openapi.Generator, basePath string) {
|
||||
packages := router.Group("/packages")
|
||||
groupPath := basePath + "/packages"
|
||||
|
||||
Register(packages, doc, groupPath, "GET", "", handler.List, RouteSpec{
|
||||
Summary: "套餐列表",
|
||||
Tags: []string{"套餐管理"},
|
||||
Input: new(dto.PackageListRequest),
|
||||
Output: new(dto.PackagePageResult),
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(packages, doc, groupPath, "POST", "", handler.Create, RouteSpec{
|
||||
Summary: "创建套餐",
|
||||
Tags: []string{"套餐管理"},
|
||||
Input: new(dto.CreatePackageRequest),
|
||||
Output: new(dto.PackageResponse),
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(packages, doc, groupPath, "GET", "/:id", handler.Get, RouteSpec{
|
||||
Summary: "获取套餐详情",
|
||||
Tags: []string{"套餐管理"},
|
||||
Input: new(dto.IDReq),
|
||||
Output: new(dto.PackageResponse),
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(packages, doc, groupPath, "PUT", "/:id", handler.Update, RouteSpec{
|
||||
Summary: "更新套餐",
|
||||
Tags: []string{"套餐管理"},
|
||||
Input: new(dto.UpdatePackageParams),
|
||||
Output: new(dto.PackageResponse),
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(packages, doc, groupPath, "DELETE", "/:id", handler.Delete, RouteSpec{
|
||||
Summary: "删除套餐",
|
||||
Tags: []string{"套餐管理"},
|
||||
Input: new(dto.IDReq),
|
||||
Output: nil,
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(packages, doc, groupPath, "PATCH", "/:id/status", handler.UpdateStatus, RouteSpec{
|
||||
Summary: "更新套餐状态",
|
||||
Tags: []string{"套餐管理"},
|
||||
Input: new(dto.UpdatePackageStatusParams),
|
||||
Output: nil,
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(packages, doc, groupPath, "PATCH", "/:id/shelf", handler.UpdateShelfStatus, RouteSpec{
|
||||
Summary: "更新套餐上架状态",
|
||||
Tags: []string{"套餐管理"},
|
||||
Input: new(dto.UpdatePackageShelfStatusParams),
|
||||
Output: nil,
|
||||
Auth: true,
|
||||
})
|
||||
}
|
||||
62
internal/routes/package_series.go
Normal file
62
internal/routes/package_series.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/handler/admin"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/openapi"
|
||||
)
|
||||
|
||||
func registerPackageSeriesRoutes(router fiber.Router, handler *admin.PackageSeriesHandler, doc *openapi.Generator, basePath string) {
|
||||
series := router.Group("/package-series")
|
||||
groupPath := basePath + "/package-series"
|
||||
|
||||
Register(series, doc, groupPath, "GET", "", handler.List, RouteSpec{
|
||||
Summary: "套餐系列列表",
|
||||
Tags: []string{"套餐系列管理"},
|
||||
Input: new(dto.PackageSeriesListRequest),
|
||||
Output: new(dto.PackageSeriesPageResult),
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(series, doc, groupPath, "POST", "", handler.Create, RouteSpec{
|
||||
Summary: "创建套餐系列",
|
||||
Tags: []string{"套餐系列管理"},
|
||||
Input: new(dto.CreatePackageSeriesRequest),
|
||||
Output: new(dto.PackageSeriesResponse),
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(series, doc, groupPath, "GET", "/:id", handler.Get, RouteSpec{
|
||||
Summary: "获取套餐系列详情",
|
||||
Tags: []string{"套餐系列管理"},
|
||||
Input: new(dto.IDReq),
|
||||
Output: new(dto.PackageSeriesResponse),
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(series, doc, groupPath, "PUT", "/:id", handler.Update, RouteSpec{
|
||||
Summary: "更新套餐系列",
|
||||
Tags: []string{"套餐系列管理"},
|
||||
Input: new(dto.UpdatePackageSeriesParams),
|
||||
Output: new(dto.PackageSeriesResponse),
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(series, doc, groupPath, "DELETE", "/:id", handler.Delete, RouteSpec{
|
||||
Summary: "删除套餐系列",
|
||||
Tags: []string{"套餐系列管理"},
|
||||
Input: new(dto.IDReq),
|
||||
Output: nil,
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(series, doc, groupPath, "PATCH", "/:id/status", handler.UpdateStatus, RouteSpec{
|
||||
Summary: "更新套餐系列状态",
|
||||
Tags: []string{"套餐系列管理"},
|
||||
Input: new(dto.UpdatePackageSeriesStatusParams),
|
||||
Output: nil,
|
||||
Auth: true,
|
||||
})
|
||||
}
|
||||
305
internal/service/package/service.go
Normal file
305
internal/service/package/service.go
Normal file
@@ -0,0 +1,305 @@
|
||||
package packagepkg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
packageStore *postgres.PackageStore
|
||||
packageSeriesStore *postgres.PackageSeriesStore
|
||||
}
|
||||
|
||||
func New(packageStore *postgres.PackageStore, packageSeriesStore *postgres.PackageSeriesStore) *Service {
|
||||
return &Service{
|
||||
packageStore: packageStore,
|
||||
packageSeriesStore: packageSeriesStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Create(ctx context.Context, req *dto.CreatePackageRequest) (*dto.PackageResponse, error) {
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
existing, _ := s.packageStore.GetByCode(ctx, req.PackageCode)
|
||||
if existing != nil {
|
||||
return nil, errors.New(errors.CodeConflict, "套餐编码已存在")
|
||||
}
|
||||
|
||||
if req.SeriesID != nil && *req.SeriesID > 0 {
|
||||
_, err := s.packageSeriesStore.GetByID(ctx, *req.SeriesID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "套餐系列不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取套餐系列失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: req.PackageCode,
|
||||
PackageName: req.PackageName,
|
||||
PackageType: req.PackageType,
|
||||
DurationMonths: req.DurationMonths,
|
||||
Price: req.Price,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 2,
|
||||
}
|
||||
if req.SeriesID != nil {
|
||||
pkg.SeriesID = *req.SeriesID
|
||||
}
|
||||
if req.DataType != nil {
|
||||
pkg.DataType = *req.DataType
|
||||
}
|
||||
if req.RealDataMB != nil {
|
||||
pkg.RealDataMB = *req.RealDataMB
|
||||
}
|
||||
if req.VirtualDataMB != nil {
|
||||
pkg.VirtualDataMB = *req.VirtualDataMB
|
||||
}
|
||||
if req.DataAmountMB != nil {
|
||||
pkg.DataAmountMB = *req.DataAmountMB
|
||||
}
|
||||
if req.SuggestedCostPrice != nil {
|
||||
pkg.SuggestedCostPrice = *req.SuggestedCostPrice
|
||||
}
|
||||
if req.SuggestedRetailPrice != nil {
|
||||
pkg.SuggestedRetailPrice = *req.SuggestedRetailPrice
|
||||
}
|
||||
pkg.Creator = currentUserID
|
||||
|
||||
if err := s.packageStore.Create(ctx, pkg); err != nil {
|
||||
return nil, fmt.Errorf("创建套餐失败: %w", err)
|
||||
}
|
||||
|
||||
return s.toResponse(pkg), nil
|
||||
}
|
||||
|
||||
func (s *Service) Get(ctx context.Context, id uint) (*dto.PackageResponse, error) {
|
||||
pkg, err := s.packageStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "套餐不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取套餐失败: %w", err)
|
||||
}
|
||||
return s.toResponse(pkg), nil
|
||||
}
|
||||
|
||||
func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdatePackageRequest) (*dto.PackageResponse, error) {
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
pkg, err := s.packageStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "套餐不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取套餐失败: %w", err)
|
||||
}
|
||||
|
||||
if req.SeriesID != nil && *req.SeriesID > 0 {
|
||||
_, err := s.packageSeriesStore.GetByID(ctx, *req.SeriesID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "套餐系列不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取套餐系列失败: %w", err)
|
||||
}
|
||||
pkg.SeriesID = *req.SeriesID
|
||||
}
|
||||
|
||||
if req.PackageName != nil {
|
||||
pkg.PackageName = *req.PackageName
|
||||
}
|
||||
if req.PackageType != nil {
|
||||
pkg.PackageType = *req.PackageType
|
||||
}
|
||||
if req.DurationMonths != nil {
|
||||
pkg.DurationMonths = *req.DurationMonths
|
||||
}
|
||||
if req.DataType != nil {
|
||||
pkg.DataType = *req.DataType
|
||||
}
|
||||
if req.RealDataMB != nil {
|
||||
pkg.RealDataMB = *req.RealDataMB
|
||||
}
|
||||
if req.VirtualDataMB != nil {
|
||||
pkg.VirtualDataMB = *req.VirtualDataMB
|
||||
}
|
||||
if req.DataAmountMB != nil {
|
||||
pkg.DataAmountMB = *req.DataAmountMB
|
||||
}
|
||||
if req.Price != nil {
|
||||
pkg.Price = *req.Price
|
||||
}
|
||||
if req.SuggestedCostPrice != nil {
|
||||
pkg.SuggestedCostPrice = *req.SuggestedCostPrice
|
||||
}
|
||||
if req.SuggestedRetailPrice != nil {
|
||||
pkg.SuggestedRetailPrice = *req.SuggestedRetailPrice
|
||||
}
|
||||
pkg.Updater = currentUserID
|
||||
|
||||
if err := s.packageStore.Update(ctx, pkg); err != nil {
|
||||
return nil, fmt.Errorf("更新套餐失败: %w", err)
|
||||
}
|
||||
|
||||
return s.toResponse(pkg), nil
|
||||
}
|
||||
|
||||
func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
_, err := s.packageStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeNotFound, "套餐不存在")
|
||||
}
|
||||
return fmt.Errorf("获取套餐失败: %w", err)
|
||||
}
|
||||
|
||||
if err := s.packageStore.Delete(ctx, id); err != nil {
|
||||
return fmt.Errorf("删除套餐失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) List(ctx context.Context, req *dto.PackageListRequest) ([]*dto.PackageResponse, int64, error) {
|
||||
opts := &store.QueryOptions{
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
OrderBy: "id DESC",
|
||||
}
|
||||
if opts.Page == 0 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.PageSize == 0 {
|
||||
opts.PageSize = constants.DefaultPageSize
|
||||
}
|
||||
|
||||
filters := make(map[string]interface{})
|
||||
if req.PackageName != nil {
|
||||
filters["package_name"] = *req.PackageName
|
||||
}
|
||||
if req.SeriesID != nil {
|
||||
filters["series_id"] = *req.SeriesID
|
||||
}
|
||||
if req.Status != nil {
|
||||
filters["status"] = *req.Status
|
||||
}
|
||||
if req.ShelfStatus != nil {
|
||||
filters["shelf_status"] = *req.ShelfStatus
|
||||
}
|
||||
if req.PackageType != nil {
|
||||
filters["package_type"] = *req.PackageType
|
||||
}
|
||||
|
||||
packages, total, err := s.packageStore.List(ctx, opts, filters)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("查询套餐列表失败: %w", err)
|
||||
}
|
||||
|
||||
responses := make([]*dto.PackageResponse, len(packages))
|
||||
for i, pkg := range packages {
|
||||
responses[i] = s.toResponse(pkg)
|
||||
}
|
||||
|
||||
return responses, total, nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
pkg, err := s.packageStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeNotFound, "套餐不存在")
|
||||
}
|
||||
return fmt.Errorf("获取套餐失败: %w", err)
|
||||
}
|
||||
|
||||
pkg.Status = status
|
||||
pkg.Updater = currentUserID
|
||||
|
||||
if status == constants.StatusDisabled {
|
||||
pkg.ShelfStatus = 2
|
||||
}
|
||||
|
||||
if err := s.packageStore.Update(ctx, pkg); err != nil {
|
||||
return fmt.Errorf("更新套餐状态失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateShelfStatus(ctx context.Context, id uint, shelfStatus int) error {
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
pkg, err := s.packageStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeNotFound, "套餐不存在")
|
||||
}
|
||||
return fmt.Errorf("获取套餐失败: %w", err)
|
||||
}
|
||||
|
||||
if shelfStatus == 1 && pkg.Status == constants.StatusDisabled {
|
||||
return errors.New(errors.CodeInvalidStatus, "禁用的套餐不能上架,请先启用")
|
||||
}
|
||||
|
||||
pkg.ShelfStatus = shelfStatus
|
||||
pkg.Updater = currentUserID
|
||||
|
||||
if err := s.packageStore.Update(ctx, pkg); err != nil {
|
||||
return fmt.Errorf("更新套餐上架状态失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) toResponse(pkg *model.Package) *dto.PackageResponse {
|
||||
var seriesID *uint
|
||||
if pkg.SeriesID > 0 {
|
||||
seriesID = &pkg.SeriesID
|
||||
}
|
||||
return &dto.PackageResponse{
|
||||
ID: pkg.ID,
|
||||
PackageCode: pkg.PackageCode,
|
||||
PackageName: pkg.PackageName,
|
||||
SeriesID: seriesID,
|
||||
PackageType: pkg.PackageType,
|
||||
DurationMonths: pkg.DurationMonths,
|
||||
DataType: pkg.DataType,
|
||||
RealDataMB: pkg.RealDataMB,
|
||||
VirtualDataMB: pkg.VirtualDataMB,
|
||||
DataAmountMB: pkg.DataAmountMB,
|
||||
Price: pkg.Price,
|
||||
SuggestedCostPrice: pkg.SuggestedCostPrice,
|
||||
SuggestedRetailPrice: pkg.SuggestedRetailPrice,
|
||||
Status: pkg.Status,
|
||||
ShelfStatus: pkg.ShelfStatus,
|
||||
CreatedAt: pkg.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: pkg.UpdatedAt.Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
@@ -122,12 +122,12 @@ func TestPackageService_UpdateStatus(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, pkg.ShelfStatus)
|
||||
|
||||
err = svc.UpdateStatus(ctx, created.ID, 2)
|
||||
err = svc.UpdateStatus(ctx, created.ID, constants.StatusDisabled)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkg, err = svc.Get(ctx, created.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, pkg.Status)
|
||||
assert.Equal(t, constants.StatusDisabled, pkg.Status)
|
||||
assert.Equal(t, 2, pkg.ShelfStatus)
|
||||
})
|
||||
|
||||
@@ -145,20 +145,20 @@ func TestPackageService_UpdateStatus(t *testing.T) {
|
||||
err = svc.UpdateShelfStatus(ctx, created2.ID, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = svc.UpdateStatus(ctx, created2.ID, 2)
|
||||
err = svc.UpdateStatus(ctx, created2.ID, constants.StatusDisabled)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkg, err := svc.Get(ctx, created2.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, pkg.Status)
|
||||
assert.Equal(t, constants.StatusDisabled, pkg.Status)
|
||||
assert.Equal(t, 2, pkg.ShelfStatus)
|
||||
|
||||
err = svc.UpdateStatus(ctx, created2.ID, 1)
|
||||
err = svc.UpdateStatus(ctx, created2.ID, constants.StatusEnabled)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkg, err = svc.Get(ctx, created2.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, pkg.Status)
|
||||
assert.Equal(t, constants.StatusEnabled, pkg.Status)
|
||||
assert.Equal(t, 2, pkg.ShelfStatus)
|
||||
})
|
||||
}
|
||||
@@ -209,7 +209,7 @@ func TestPackageService_UpdateShelfStatus(t *testing.T) {
|
||||
created, err := svc.Create(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = svc.UpdateStatus(ctx, created.ID, 2)
|
||||
err = svc.UpdateStatus(ctx, created.ID, constants.StatusDisabled)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = svc.UpdateShelfStatus(ctx, created.ID, 1)
|
||||
|
||||
177
internal/service/package_series/service.go
Normal file
177
internal/service/package_series/service.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package package_series
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
packageSeriesStore *postgres.PackageSeriesStore
|
||||
}
|
||||
|
||||
func New(packageSeriesStore *postgres.PackageSeriesStore) *Service {
|
||||
return &Service{packageSeriesStore: packageSeriesStore}
|
||||
}
|
||||
|
||||
func (s *Service) Create(ctx context.Context, req *dto.CreatePackageSeriesRequest) (*dto.PackageSeriesResponse, error) {
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
existing, _ := s.packageSeriesStore.GetByCode(ctx, req.SeriesCode)
|
||||
if existing != nil {
|
||||
return nil, errors.New(errors.CodeConflict, "系列编码已存在")
|
||||
}
|
||||
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: req.SeriesCode,
|
||||
SeriesName: req.SeriesName,
|
||||
Description: req.Description,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
series.Creator = currentUserID
|
||||
|
||||
if err := s.packageSeriesStore.Create(ctx, series); err != nil {
|
||||
return nil, fmt.Errorf("创建套餐系列失败: %w", err)
|
||||
}
|
||||
|
||||
return s.toResponse(series), nil
|
||||
}
|
||||
|
||||
func (s *Service) Get(ctx context.Context, id uint) (*dto.PackageSeriesResponse, error) {
|
||||
series, err := s.packageSeriesStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "套餐系列不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取套餐系列失败: %w", err)
|
||||
}
|
||||
return s.toResponse(series), nil
|
||||
}
|
||||
|
||||
func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdatePackageSeriesRequest) (*dto.PackageSeriesResponse, error) {
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
series, err := s.packageSeriesStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "套餐系列不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取套餐系列失败: %w", err)
|
||||
}
|
||||
|
||||
if req.SeriesName != nil {
|
||||
series.SeriesName = *req.SeriesName
|
||||
}
|
||||
if req.Description != nil {
|
||||
series.Description = *req.Description
|
||||
}
|
||||
series.Updater = currentUserID
|
||||
|
||||
if err := s.packageSeriesStore.Update(ctx, series); err != nil {
|
||||
return nil, fmt.Errorf("更新套餐系列失败: %w", err)
|
||||
}
|
||||
|
||||
return s.toResponse(series), nil
|
||||
}
|
||||
|
||||
func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
_, err := s.packageSeriesStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeNotFound, "套餐系列不存在")
|
||||
}
|
||||
return fmt.Errorf("获取套餐系列失败: %w", err)
|
||||
}
|
||||
|
||||
if err := s.packageSeriesStore.Delete(ctx, id); err != nil {
|
||||
return fmt.Errorf("删除套餐系列失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) List(ctx context.Context, req *dto.PackageSeriesListRequest) ([]*dto.PackageSeriesResponse, int64, error) {
|
||||
opts := &store.QueryOptions{
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
OrderBy: "id DESC",
|
||||
}
|
||||
if opts.Page == 0 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.PageSize == 0 {
|
||||
opts.PageSize = constants.DefaultPageSize
|
||||
}
|
||||
|
||||
filters := make(map[string]interface{})
|
||||
if req.SeriesName != nil {
|
||||
filters["series_name"] = *req.SeriesName
|
||||
}
|
||||
if req.Status != nil {
|
||||
filters["status"] = *req.Status
|
||||
}
|
||||
|
||||
seriesList, total, err := s.packageSeriesStore.List(ctx, opts, filters)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("查询套餐系列列表失败: %w", err)
|
||||
}
|
||||
|
||||
responses := make([]*dto.PackageSeriesResponse, len(seriesList))
|
||||
for i, series := range seriesList {
|
||||
responses[i] = s.toResponse(series)
|
||||
}
|
||||
|
||||
return responses, total, nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
series, err := s.packageSeriesStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeNotFound, "套餐系列不存在")
|
||||
}
|
||||
return fmt.Errorf("获取套餐系列失败: %w", err)
|
||||
}
|
||||
|
||||
series.Status = status
|
||||
series.Updater = currentUserID
|
||||
|
||||
if err := s.packageSeriesStore.Update(ctx, series); err != nil {
|
||||
return fmt.Errorf("更新套餐系列状态失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) toResponse(series *model.PackageSeries) *dto.PackageSeriesResponse {
|
||||
return &dto.PackageSeriesResponse{
|
||||
ID: series.ID,
|
||||
SeriesCode: series.SeriesCode,
|
||||
SeriesName: series.SeriesName,
|
||||
Description: series.Description,
|
||||
Status: series.Status,
|
||||
CreatedAt: series.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: series.UpdatedAt.Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
313
internal/service/package_series/service_test.go
Normal file
313
internal/service/package_series/service_test.go
Normal file
@@ -0,0 +1,313 @@
|
||||
package package_series
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPackageSeriesService_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
store := postgres.NewPackageSeriesStore(tx)
|
||||
svc := New(store)
|
||||
|
||||
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
|
||||
UserID: 1,
|
||||
UserType: constants.UserTypePlatform,
|
||||
})
|
||||
|
||||
t.Run("创建成功", func(t *testing.T) {
|
||||
seriesCode := fmt.Sprintf("SVC_CREATE_%d", time.Now().UnixNano())
|
||||
req := &dto.CreatePackageSeriesRequest{
|
||||
SeriesCode: seriesCode,
|
||||
SeriesName: "测试套餐系列",
|
||||
Description: "服务层测试",
|
||||
}
|
||||
|
||||
resp, err := svc.Create(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, resp.ID)
|
||||
assert.Equal(t, req.SeriesCode, resp.SeriesCode)
|
||||
assert.Equal(t, req.SeriesName, resp.SeriesName)
|
||||
assert.Equal(t, constants.StatusEnabled, resp.Status)
|
||||
})
|
||||
|
||||
t.Run("编码重复失败", func(t *testing.T) {
|
||||
seriesCode := fmt.Sprintf("SVC_DUP_%d", time.Now().UnixNano())
|
||||
req1 := &dto.CreatePackageSeriesRequest{
|
||||
SeriesCode: seriesCode,
|
||||
SeriesName: "第一个系列",
|
||||
Description: "测试重复",
|
||||
}
|
||||
|
||||
_, err := svc.Create(ctx, req1)
|
||||
require.NoError(t, err)
|
||||
|
||||
req2 := &dto.CreatePackageSeriesRequest{
|
||||
SeriesCode: seriesCode,
|
||||
SeriesName: "第二个系列",
|
||||
Description: "重复编码",
|
||||
}
|
||||
|
||||
_, err = svc.Create(ctx, req2)
|
||||
require.Error(t, err)
|
||||
appErr, ok := err.(*errors.AppError)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, errors.CodeConflict, appErr.Code)
|
||||
})
|
||||
|
||||
t.Run("未授权失败", func(t *testing.T) {
|
||||
req := &dto.CreatePackageSeriesRequest{
|
||||
SeriesCode: fmt.Sprintf("SVC_UNAUTH_%d", time.Now().UnixNano()),
|
||||
SeriesName: "未授权测试",
|
||||
Description: "无用户上下文",
|
||||
}
|
||||
|
||||
_, err := svc.Create(context.Background(), req)
|
||||
require.Error(t, err)
|
||||
appErr, ok := err.(*errors.AppError)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, errors.CodeUnauthorized, appErr.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageSeriesService_Get(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
store := postgres.NewPackageSeriesStore(tx)
|
||||
svc := New(store)
|
||||
|
||||
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
|
||||
UserID: 1,
|
||||
UserType: constants.UserTypePlatform,
|
||||
})
|
||||
|
||||
seriesCode := fmt.Sprintf("SVC_GET_%d", time.Now().UnixNano())
|
||||
req := &dto.CreatePackageSeriesRequest{
|
||||
SeriesCode: seriesCode,
|
||||
SeriesName: "查询测试",
|
||||
Description: "用于查询测试",
|
||||
}
|
||||
created, err := svc.Create(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("获取存在的系列", func(t *testing.T) {
|
||||
resp, err := svc.Get(ctx, created.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, created.SeriesCode, resp.SeriesCode)
|
||||
assert.Equal(t, created.SeriesName, resp.SeriesName)
|
||||
})
|
||||
|
||||
t.Run("获取不存在的系列", func(t *testing.T) {
|
||||
_, err := svc.Get(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
appErr, ok := err.(*errors.AppError)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, errors.CodeNotFound, appErr.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageSeriesService_Update(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
store := postgres.NewPackageSeriesStore(tx)
|
||||
svc := New(store)
|
||||
|
||||
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
|
||||
UserID: 1,
|
||||
UserType: constants.UserTypePlatform,
|
||||
})
|
||||
|
||||
seriesCode := fmt.Sprintf("SVC_UPD_%d", time.Now().UnixNano())
|
||||
req := &dto.CreatePackageSeriesRequest{
|
||||
SeriesCode: seriesCode,
|
||||
SeriesName: "更新测试",
|
||||
Description: "原始描述",
|
||||
}
|
||||
created, err := svc.Create(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("更新成功", func(t *testing.T) {
|
||||
newName := "更新后的名称"
|
||||
newDesc := "更新后的描述"
|
||||
updateReq := &dto.UpdatePackageSeriesRequest{
|
||||
SeriesName: &newName,
|
||||
Description: &newDesc,
|
||||
}
|
||||
|
||||
resp, err := svc.Update(ctx, created.ID, updateReq)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, newName, resp.SeriesName)
|
||||
assert.Equal(t, newDesc, resp.Description)
|
||||
})
|
||||
|
||||
t.Run("更新不存在的系列", func(t *testing.T) {
|
||||
newName := "test"
|
||||
updateReq := &dto.UpdatePackageSeriesRequest{
|
||||
SeriesName: &newName,
|
||||
}
|
||||
|
||||
_, err := svc.Update(ctx, 99999, updateReq)
|
||||
require.Error(t, err)
|
||||
appErr, ok := err.(*errors.AppError)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, errors.CodeNotFound, appErr.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageSeriesService_Delete(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
store := postgres.NewPackageSeriesStore(tx)
|
||||
svc := New(store)
|
||||
|
||||
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
|
||||
UserID: 1,
|
||||
UserType: constants.UserTypePlatform,
|
||||
})
|
||||
|
||||
seriesCode := fmt.Sprintf("SVC_DEL_%d", time.Now().UnixNano())
|
||||
req := &dto.CreatePackageSeriesRequest{
|
||||
SeriesCode: seriesCode,
|
||||
SeriesName: "删除测试",
|
||||
Description: "用于删除测试",
|
||||
}
|
||||
created, err := svc.Create(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("删除成功", func(t *testing.T) {
|
||||
err := svc.Delete(ctx, created.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = svc.Get(ctx, created.ID)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("删除不存在的系列", func(t *testing.T) {
|
||||
err := svc.Delete(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageSeriesService_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
store := postgres.NewPackageSeriesStore(tx)
|
||||
svc := New(store)
|
||||
|
||||
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
|
||||
UserID: 1,
|
||||
UserType: constants.UserTypePlatform,
|
||||
})
|
||||
|
||||
seriesList := []dto.CreatePackageSeriesRequest{
|
||||
{
|
||||
SeriesCode: fmt.Sprintf("SVC_LIST_001_%d", time.Now().UnixNano()),
|
||||
SeriesName: "基础套餐",
|
||||
Description: "列表测试1",
|
||||
},
|
||||
{
|
||||
SeriesCode: fmt.Sprintf("SVC_LIST_002_%d", time.Now().UnixNano()),
|
||||
SeriesName: "高级套餐",
|
||||
Description: "列表测试2",
|
||||
},
|
||||
{
|
||||
SeriesCode: fmt.Sprintf("SVC_LIST_003_%d", time.Now().UnixNano()),
|
||||
SeriesName: "企业套餐",
|
||||
Description: "列表测试3",
|
||||
},
|
||||
}
|
||||
for _, s := range seriesList {
|
||||
_, err := svc.Create(ctx, &s)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
t.Run("查询列表", func(t *testing.T) {
|
||||
req := &dto.PackageSeriesListRequest{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
}
|
||||
result, total, err := svc.List(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.GreaterOrEqual(t, len(result), 3)
|
||||
})
|
||||
|
||||
t.Run("按状态过滤", func(t *testing.T) {
|
||||
status := constants.StatusEnabled
|
||||
req := &dto.PackageSeriesListRequest{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
Status: &status,
|
||||
}
|
||||
result, total, err := svc.List(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
for _, s := range result {
|
||||
assert.Equal(t, constants.StatusEnabled, s.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按名称模糊搜索", func(t *testing.T) {
|
||||
seriesName := "高级"
|
||||
req := &dto.PackageSeriesListRequest{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
SeriesName: &seriesName,
|
||||
}
|
||||
result, total, err := svc.List(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
assert.GreaterOrEqual(t, len(result), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageSeriesService_UpdateStatus(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
store := postgres.NewPackageSeriesStore(tx)
|
||||
svc := New(store)
|
||||
|
||||
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
|
||||
UserID: 1,
|
||||
UserType: constants.UserTypePlatform,
|
||||
})
|
||||
|
||||
seriesCode := fmt.Sprintf("SVC_STATUS_%d", time.Now().UnixNano())
|
||||
req := &dto.CreatePackageSeriesRequest{
|
||||
SeriesCode: seriesCode,
|
||||
SeriesName: "状态测试",
|
||||
Description: "用于状态更新测试",
|
||||
}
|
||||
created, err := svc.Create(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, constants.StatusEnabled, created.Status)
|
||||
|
||||
t.Run("禁用系列", func(t *testing.T) {
|
||||
err := svc.UpdateStatus(ctx, created.ID, constants.StatusDisabled)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := svc.Get(ctx, created.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, constants.StatusDisabled, updated.Status)
|
||||
})
|
||||
|
||||
t.Run("启用系列", func(t *testing.T) {
|
||||
err := svc.UpdateStatus(ctx, created.ID, constants.StatusEnabled)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := svc.Get(ctx, created.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, constants.StatusEnabled, updated.Status)
|
||||
})
|
||||
|
||||
t.Run("更新不存在的系列状态", func(t *testing.T) {
|
||||
err := svc.UpdateStatus(ctx, 99999, constants.StatusDisabled)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
@@ -11,6 +13,10 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func uniqueICCIDPrefix() string {
|
||||
return fmt.Sprintf("T%d", time.Now().UnixNano()%1000000000)
|
||||
}
|
||||
|
||||
func TestIotCardStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
@@ -94,15 +100,16 @@ func TestIotCardStore_ListStandalone(t *testing.T) {
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueICCIDPrefix()
|
||||
standaloneCards := []*model.IotCard{
|
||||
{ICCID: "89860012345678903001", CardType: "data_card", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678903002", CardType: "data_card", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678903003", CardType: "data_card", CarrierID: 2, Status: 2},
|
||||
{ICCID: prefix + "0001", CardType: "data_card", CarrierID: 1, Status: 1},
|
||||
{ICCID: prefix + "0002", CardType: "data_card", CarrierID: 1, Status: 1},
|
||||
{ICCID: prefix + "0003", CardType: "data_card", CarrierID: 2, Status: 2},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, standaloneCards))
|
||||
|
||||
boundCard := &model.IotCard{
|
||||
ICCID: "89860012345678903004",
|
||||
ICCID: prefix + "0004",
|
||||
CardType: "data_card",
|
||||
CarrierID: 1,
|
||||
Status: 1,
|
||||
@@ -117,7 +124,8 @@ func TestIotCardStore_ListStandalone(t *testing.T) {
|
||||
require.NoError(t, tx.Create(binding).Error)
|
||||
|
||||
t.Run("查询所有单卡", func(t *testing.T) {
|
||||
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
|
||||
filters := map[string]interface{}{"iccid": prefix}
|
||||
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), total)
|
||||
assert.Len(t, cards, 3)
|
||||
@@ -128,7 +136,7 @@ func TestIotCardStore_ListStandalone(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("按运营商ID过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"carrier_id": uint(1)}
|
||||
filters := map[string]interface{}{"carrier_id": uint(1), "iccid": prefix}
|
||||
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
@@ -138,7 +146,7 @@ func TestIotCardStore_ListStandalone(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("按状态过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": 2}
|
||||
filters := map[string]interface{}{"status": 2, "iccid": prefix}
|
||||
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
@@ -147,26 +155,28 @@ func TestIotCardStore_ListStandalone(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("按ICCID模糊查询", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"iccid": "903001"}
|
||||
filters := map[string]interface{}{"iccid": prefix + "0001"}
|
||||
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Contains(t, cards[0].ICCID, "903001")
|
||||
assert.Contains(t, cards[0].ICCID, prefix+"0001")
|
||||
})
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, nil)
|
||||
filters := map[string]interface{}{"iccid": prefix}
|
||||
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), total)
|
||||
assert.Len(t, cards, 2)
|
||||
|
||||
cards2, _, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 2, PageSize: 2}, nil)
|
||||
cards2, _, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 2, PageSize: 2}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, cards2, 1)
|
||||
})
|
||||
|
||||
t.Run("默认分页选项", func(t *testing.T) {
|
||||
cards, total, err := s.ListStandalone(ctx, nil, nil)
|
||||
filters := map[string]interface{}{"iccid": prefix}
|
||||
cards, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), total)
|
||||
assert.Len(t, cards, 3)
|
||||
@@ -181,39 +191,43 @@ func TestIotCardStore_ListStandalone_Filters(t *testing.T) {
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
shopID := uint(100)
|
||||
prefix := uniqueICCIDPrefix()
|
||||
batchPrefix := "B" + prefix
|
||||
msisdnPrefix := "199" + prefix[1:8]
|
||||
shopID := uint(time.Now().UnixNano() % 1000000)
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678904001", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: &shopID, BatchNo: "BATCH001", MSISDN: "13800000001"},
|
||||
{ICCID: "89860012345678904002", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: nil, BatchNo: "BATCH001", MSISDN: "13800000002"},
|
||||
{ICCID: "89860012345678904003", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: nil, BatchNo: "BATCH002", MSISDN: "13800000003"},
|
||||
{ICCID: prefix + "A001", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: &shopID, BatchNo: batchPrefix + "01", MSISDN: msisdnPrefix + "01"},
|
||||
{ICCID: prefix + "A002", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: nil, BatchNo: batchPrefix + "01", MSISDN: msisdnPrefix + "02"},
|
||||
{ICCID: prefix + "A003", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: nil, BatchNo: batchPrefix + "02", MSISDN: msisdnPrefix + "03"},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
t.Run("按店铺ID过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"shop_id": shopID}
|
||||
cards, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
filters := map[string]interface{}{"shop_id": shopID, "iccid": prefix}
|
||||
result, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Equal(t, shopID, *cards[0].ShopID)
|
||||
assert.Equal(t, shopID, *result[0].ShopID)
|
||||
})
|
||||
|
||||
t.Run("按批次号过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"batch_no": "BATCH001"}
|
||||
filters := map[string]interface{}{"batch_no": batchPrefix + "01", "iccid": prefix}
|
||||
_, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
})
|
||||
|
||||
t.Run("按MSISDN模糊查询", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"msisdn": "000001"}
|
||||
filters := map[string]interface{}{"msisdn": msisdnPrefix + "01"}
|
||||
result, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Contains(t, result[0].MSISDN, "000001")
|
||||
assert.Contains(t, result[0].MSISDN, msisdnPrefix+"01")
|
||||
})
|
||||
|
||||
t.Run("已分销过滤-true", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"is_distributed": true}
|
||||
filters := map[string]interface{}{"is_distributed": true, "iccid": prefix}
|
||||
result, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
@@ -221,7 +235,7 @@ func TestIotCardStore_ListStandalone_Filters(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("已分销过滤-false", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"is_distributed": false}
|
||||
filters := map[string]interface{}{"is_distributed": false, "iccid": prefix}
|
||||
result, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
@@ -232,8 +246,8 @@ func TestIotCardStore_ListStandalone_Filters(t *testing.T) {
|
||||
|
||||
t.Run("ICCID范围查询", func(t *testing.T) {
|
||||
filters := map[string]interface{}{
|
||||
"iccid_start": "89860012345678904001",
|
||||
"iccid_end": "89860012345678904002",
|
||||
"iccid_start": prefix + "A001",
|
||||
"iccid_end": prefix + "A002",
|
||||
}
|
||||
_, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
|
||||
84
internal/store/postgres/package_series_store.go
Normal file
84
internal/store/postgres/package_series_store.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
)
|
||||
|
||||
type PackageSeriesStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewPackageSeriesStore(db *gorm.DB) *PackageSeriesStore {
|
||||
return &PackageSeriesStore{db: db}
|
||||
}
|
||||
|
||||
func (s *PackageSeriesStore) Create(ctx context.Context, series *model.PackageSeries) error {
|
||||
return s.db.WithContext(ctx).Create(series).Error
|
||||
}
|
||||
|
||||
func (s *PackageSeriesStore) GetByID(ctx context.Context, id uint) (*model.PackageSeries, error) {
|
||||
var series model.PackageSeries
|
||||
if err := s.db.WithContext(ctx).First(&series, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &series, nil
|
||||
}
|
||||
|
||||
func (s *PackageSeriesStore) GetByCode(ctx context.Context, code string) (*model.PackageSeries, error) {
|
||||
var series model.PackageSeries
|
||||
if err := s.db.WithContext(ctx).Where("series_code = ?", code).First(&series).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &series, nil
|
||||
}
|
||||
|
||||
func (s *PackageSeriesStore) Update(ctx context.Context, series *model.PackageSeries) error {
|
||||
return s.db.WithContext(ctx).Save(series).Error
|
||||
}
|
||||
|
||||
func (s *PackageSeriesStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.PackageSeries{}, id).Error
|
||||
}
|
||||
|
||||
func (s *PackageSeriesStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.PackageSeries, int64, error) {
|
||||
var seriesList []*model.PackageSeries
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.PackageSeries{})
|
||||
|
||||
if seriesName, ok := filters["series_name"].(string); ok && seriesName != "" {
|
||||
query = query.Where("series_name LIKE ?", "%"+seriesName+"%")
|
||||
}
|
||||
if status, ok := filters["status"]; ok {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if opts == nil {
|
||||
opts = store.DefaultQueryOptions()
|
||||
}
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
}
|
||||
|
||||
if err := query.Find(&seriesList).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return seriesList, total, nil
|
||||
}
|
||||
|
||||
func (s *PackageSeriesStore) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||
return s.db.WithContext(ctx).Model(&model.PackageSeries{}).Where("id = ?", id).Update("status", status).Error
|
||||
}
|
||||
191
internal/store/postgres/package_series_store_test.go
Normal file
191
internal/store/postgres/package_series_store_test.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPackageSeriesStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: "SERIES_TEST_001",
|
||||
SeriesName: "测试系列",
|
||||
Description: "测试描述",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, series)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, series.ID)
|
||||
}
|
||||
|
||||
func TestPackageSeriesStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: "SERIES_TEST_002",
|
||||
SeriesName: "测试系列2",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, series))
|
||||
|
||||
t.Run("查询存在的系列", func(t *testing.T) {
|
||||
result, err := s.GetByID(ctx, series.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, series.SeriesCode, result.SeriesCode)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的系列", func(t *testing.T) {
|
||||
_, err := s.GetByID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageSeriesStore_GetByCode(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: "SERIES_TEST_003",
|
||||
SeriesName: "测试系列3",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, series))
|
||||
|
||||
t.Run("查询存在的编码", func(t *testing.T) {
|
||||
result, err := s.GetByCode(ctx, "SERIES_TEST_003")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, series.ID, result.ID)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的编码", func(t *testing.T) {
|
||||
_, err := s.GetByCode(ctx, "NOT_EXISTS")
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageSeriesStore_Update(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: "SERIES_TEST_004",
|
||||
SeriesName: "测试系列4",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, series))
|
||||
|
||||
series.SeriesName = "测试系列4-更新"
|
||||
series.Description = "更新后的描述"
|
||||
err := s.Update(ctx, series)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, series.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "测试系列4-更新", updated.SeriesName)
|
||||
assert.Equal(t, "更新后的描述", updated.Description)
|
||||
}
|
||||
|
||||
func TestPackageSeriesStore_Delete(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: "SERIES_DEL_001",
|
||||
SeriesName: "待删除系列",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, series))
|
||||
|
||||
err := s.Delete(ctx, series.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.GetByID(ctx, series.ID)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestPackageSeriesStore_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
seriesList := []*model.PackageSeries{
|
||||
{SeriesCode: "LIST_S_001", SeriesName: "基础套餐", Status: constants.StatusEnabled},
|
||||
{SeriesCode: "LIST_S_002", SeriesName: "高级套餐", Status: constants.StatusEnabled},
|
||||
{SeriesCode: "LIST_S_003", SeriesName: "企业套餐", Status: constants.StatusEnabled},
|
||||
}
|
||||
for _, series := range seriesList {
|
||||
require.NoError(t, s.Create(ctx, series))
|
||||
}
|
||||
seriesList[2].Status = constants.StatusDisabled
|
||||
require.NoError(t, s.Update(ctx, seriesList[2]))
|
||||
|
||||
t.Run("查询所有系列", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.GreaterOrEqual(t, len(result), 3)
|
||||
})
|
||||
|
||||
t.Run("按名称模糊搜索", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"series_name": "高级"}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, series := range result {
|
||||
assert.Contains(t, series.SeriesName, "高级")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": constants.StatusDisabled}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, series := range result {
|
||||
assert.Equal(t, constants.StatusDisabled, series.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.LessOrEqual(t, len(result), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageSeriesStore_UpdateStatus(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: "STATUS_TEST_001",
|
||||
SeriesName: "状态测试系列",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, series))
|
||||
|
||||
err := s.UpdateStatus(ctx, series.ID, constants.StatusDisabled)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, series.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, constants.StatusDisabled, updated.Status)
|
||||
}
|
||||
97
internal/store/postgres/package_store.go
Normal file
97
internal/store/postgres/package_store.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
)
|
||||
|
||||
type PackageStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewPackageStore(db *gorm.DB) *PackageStore {
|
||||
return &PackageStore{db: db}
|
||||
}
|
||||
|
||||
func (s *PackageStore) Create(ctx context.Context, pkg *model.Package) error {
|
||||
return s.db.WithContext(ctx).Create(pkg).Error
|
||||
}
|
||||
|
||||
func (s *PackageStore) GetByID(ctx context.Context, id uint) (*model.Package, error) {
|
||||
var pkg model.Package
|
||||
if err := s.db.WithContext(ctx).First(&pkg, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pkg, nil
|
||||
}
|
||||
|
||||
func (s *PackageStore) GetByCode(ctx context.Context, code string) (*model.Package, error) {
|
||||
var pkg model.Package
|
||||
if err := s.db.WithContext(ctx).Where("package_code = ?", code).First(&pkg).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pkg, nil
|
||||
}
|
||||
|
||||
func (s *PackageStore) Update(ctx context.Context, pkg *model.Package) error {
|
||||
return s.db.WithContext(ctx).Save(pkg).Error
|
||||
}
|
||||
|
||||
func (s *PackageStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.Package{}, id).Error
|
||||
}
|
||||
|
||||
func (s *PackageStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.Package, int64, error) {
|
||||
var packages []*model.Package
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.Package{})
|
||||
|
||||
if packageName, ok := filters["package_name"].(string); ok && packageName != "" {
|
||||
query = query.Where("package_name LIKE ?", "%"+packageName+"%")
|
||||
}
|
||||
if seriesID, ok := filters["series_id"].(uint); ok && seriesID > 0 {
|
||||
query = query.Where("series_id = ?", seriesID)
|
||||
}
|
||||
if status, ok := filters["status"]; ok {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
if shelfStatus, ok := filters["shelf_status"]; ok {
|
||||
query = query.Where("shelf_status = ?", shelfStatus)
|
||||
}
|
||||
if packageType, ok := filters["package_type"].(string); ok && packageType != "" {
|
||||
query = query.Where("package_type = ?", packageType)
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if opts == nil {
|
||||
opts = store.DefaultQueryOptions()
|
||||
}
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
}
|
||||
|
||||
if err := query.Find(&packages).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return packages, total, nil
|
||||
}
|
||||
|
||||
func (s *PackageStore) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||
return s.db.WithContext(ctx).Model(&model.Package{}).Where("id = ?", id).Update("status", status).Error
|
||||
}
|
||||
|
||||
func (s *PackageStore) UpdateShelfStatus(ctx context.Context, id uint, shelfStatus int) error {
|
||||
return s.db.WithContext(ctx).Model(&model.Package{}).Where("id = ?", id).Update("shelf_status", shelfStatus).Error
|
||||
}
|
||||
332
internal/store/postgres/package_store_test.go
Normal file
332
internal/store/postgres/package_store_test.go
Normal file
@@ -0,0 +1,332 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPackageStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "PKG_TEST_001",
|
||||
PackageName: "测试套餐",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 1024,
|
||||
DataAmountMB: 1024,
|
||||
Price: 9900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, pkg)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, pkg.ID)
|
||||
}
|
||||
|
||||
func TestPackageStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "PKG_TEST_002",
|
||||
PackageName: "测试套餐2",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 2048,
|
||||
DataAmountMB: 2048,
|
||||
Price: 19900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
t.Run("查询存在的套餐", func(t *testing.T) {
|
||||
result, err := s.GetByID(ctx, pkg.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pkg.PackageCode, result.PackageCode)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的套餐", func(t *testing.T) {
|
||||
_, err := s.GetByID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageStore_GetByCode(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "PKG_TEST_003",
|
||||
PackageName: "测试套餐3",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 3072,
|
||||
DataAmountMB: 3072,
|
||||
Price: 29900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
t.Run("查询存在的编码", func(t *testing.T) {
|
||||
result, err := s.GetByCode(ctx, "PKG_TEST_003")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pkg.ID, result.ID)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的编码", func(t *testing.T) {
|
||||
_, err := s.GetByCode(ctx, "NOT_EXISTS")
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageStore_Update(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "PKG_TEST_004",
|
||||
PackageName: "测试套餐4",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 4096,
|
||||
DataAmountMB: 4096,
|
||||
Price: 39900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
pkg.PackageName = "测试套餐4-更新"
|
||||
pkg.Price = 49900
|
||||
err := s.Update(ctx, pkg)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, pkg.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "测试套餐4-更新", updated.PackageName)
|
||||
assert.Equal(t, int64(49900), updated.Price)
|
||||
}
|
||||
|
||||
func TestPackageStore_Delete(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "PKG_DEL_001",
|
||||
PackageName: "待删除套餐",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 1024,
|
||||
DataAmountMB: 1024,
|
||||
Price: 9900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
err := s.Delete(ctx, pkg.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.GetByID(ctx, pkg.ID)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestPackageStore_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkgList := []*model.Package{
|
||||
{
|
||||
PackageCode: "LIST_P_001",
|
||||
PackageName: "基础套餐",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 1024,
|
||||
DataAmountMB: 1024,
|
||||
Price: 9900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
},
|
||||
{
|
||||
PackageCode: "LIST_P_002",
|
||||
PackageName: "高级套餐",
|
||||
SeriesID: 2,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 12,
|
||||
DataType: "real",
|
||||
RealDataMB: 10240,
|
||||
DataAmountMB: 10240,
|
||||
Price: 99900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
},
|
||||
{
|
||||
PackageCode: "LIST_P_003",
|
||||
PackageName: "企业套餐",
|
||||
SeriesID: 3,
|
||||
PackageType: "addon",
|
||||
DurationMonths: 1,
|
||||
DataType: "virtual",
|
||||
VirtualDataMB: 5120,
|
||||
DataAmountMB: 5120,
|
||||
Price: 4900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 2,
|
||||
},
|
||||
}
|
||||
for _, pkg := range pkgList {
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
}
|
||||
pkgList[2].Status = constants.StatusDisabled
|
||||
require.NoError(t, s.Update(ctx, pkgList[2]))
|
||||
|
||||
t.Run("查询所有套餐", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.GreaterOrEqual(t, len(result), 3)
|
||||
})
|
||||
|
||||
t.Run("按名称模糊搜索", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"package_name": "高级"}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, pkg := range result {
|
||||
assert.Contains(t, pkg.PackageName, "高级")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按系列筛选", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"series_id": uint(2)}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, pkg := range result {
|
||||
assert.Equal(t, uint(2), pkg.SeriesID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": constants.StatusDisabled}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, pkg := range result {
|
||||
assert.Equal(t, constants.StatusDisabled, pkg.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按上架状态过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"shelf_status": 2}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, pkg := range result {
|
||||
assert.Equal(t, 2, pkg.ShelfStatus)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按类型过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"package_type": "addon"}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, pkg := range result {
|
||||
assert.Equal(t, "addon", pkg.PackageType)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.LessOrEqual(t, len(result), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageStore_UpdateStatus(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "STATUS_TEST_001",
|
||||
PackageName: "状态测试套餐",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 1024,
|
||||
DataAmountMB: 1024,
|
||||
Price: 9900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
err := s.UpdateStatus(ctx, pkg.ID, constants.StatusDisabled)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, pkg.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, constants.StatusDisabled, updated.Status)
|
||||
}
|
||||
|
||||
func TestPackageStore_UpdateShelfStatus(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "SHELF_TEST_001",
|
||||
PackageName: "上架测试套餐",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 1024,
|
||||
DataAmountMB: 1024,
|
||||
Price: 9900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
err := s.UpdateShelfStatus(ctx, pkg.ID, 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, pkg.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, updated.ShelfStatus)
|
||||
}
|
||||
Reference in New Issue
Block a user