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 (订单支付、店铺套餐分配、一次性分佣、卡设备系列绑定)
306 lines
8.3 KiB
Go
306 lines
8.3 KiB
Go
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),
|
|
}
|
|
}
|