package my_package import ( "context" "fmt" "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 { seriesAllocationStore *postgres.ShopSeriesAllocationStore packageAllocationStore *postgres.ShopPackageAllocationStore packageSeriesStore *postgres.PackageSeriesStore packageStore *postgres.PackageStore shopStore *postgres.ShopStore } func New( seriesAllocationStore *postgres.ShopSeriesAllocationStore, packageAllocationStore *postgres.ShopPackageAllocationStore, packageSeriesStore *postgres.PackageSeriesStore, packageStore *postgres.PackageStore, shopStore *postgres.ShopStore, ) *Service { return &Service{ seriesAllocationStore: seriesAllocationStore, packageAllocationStore: packageAllocationStore, packageSeriesStore: packageSeriesStore, packageStore: packageStore, shopStore: shopStore, } } func (s *Service) ListMyPackages(ctx context.Context, req *dto.MyPackageListRequest) ([]*dto.MyPackageResponse, int64, error) { shopID := middleware.GetShopIDFromContext(ctx) if shopID == 0 { return nil, 0, errors.New(errors.CodeUnauthorized, "当前用户不属于任何店铺") } seriesAllocations, err := s.seriesAllocationStore.GetByShopID(ctx, shopID) if err != nil { return nil, 0, fmt.Errorf("获取系列分配失败: %w", err) } if len(seriesAllocations) == 0 { return []*dto.MyPackageResponse{}, 0, nil } seriesIDs := make([]uint, 0, len(seriesAllocations)) for _, sa := range seriesAllocations { seriesIDs = append(seriesIDs, sa.SeriesID) } 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{}) filters["series_ids"] = seriesIDs filters["status"] = constants.StatusEnabled filters["shelf_status"] = 1 if req.SeriesID != nil { found := false for _, sid := range seriesIDs { if sid == *req.SeriesID { found = true break } } if !found { return []*dto.MyPackageResponse{}, 0, nil } filters["series_id"] = *req.SeriesID } 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) } packageOverrides, _ := s.packageAllocationStore.GetByShopID(ctx, shopID) overrideMap := make(map[uint]*model.ShopPackageAllocation) for _, po := range packageOverrides { overrideMap[po.PackageID] = po } allocationMap := make(map[uint]*model.ShopSeriesAllocation) for _, sa := range seriesAllocations { allocationMap[sa.SeriesID] = sa } responses := make([]*dto.MyPackageResponse, len(packages)) for i, pkg := range packages { series, _ := s.packageSeriesStore.GetByID(ctx, pkg.SeriesID) seriesName := "" if series != nil { seriesName = series.SeriesName } costPrice, priceSource := s.GetCostPrice(ctx, shopID, pkg, allocationMap, overrideMap) responses[i] = &dto.MyPackageResponse{ ID: pkg.ID, PackageCode: pkg.PackageCode, PackageName: pkg.PackageName, PackageType: pkg.PackageType, SeriesID: pkg.SeriesID, SeriesName: seriesName, CostPrice: costPrice, SuggestedRetailPrice: pkg.SuggestedRetailPrice, ProfitMargin: pkg.SuggestedRetailPrice - costPrice, PriceSource: priceSource, Status: pkg.Status, ShelfStatus: pkg.ShelfStatus, } } return responses, total, nil } func (s *Service) GetMyPackage(ctx context.Context, packageID uint) (*dto.MyPackageDetailResponse, error) { shopID := middleware.GetShopIDFromContext(ctx) if shopID == 0 { return nil, errors.New(errors.CodeUnauthorized, "当前用户不属于任何店铺") } pkg, err := s.packageStore.GetByID(ctx, packageID) if err != nil { return nil, errors.New(errors.CodeNotFound, "套餐不存在") } seriesAllocation, err := s.seriesAllocationStore.GetByShopAndSeries(ctx, shopID, pkg.SeriesID) if err != nil { return nil, errors.New(errors.CodeForbidden, "您没有该套餐的销售权限") } series, _ := s.packageSeriesStore.GetByID(ctx, pkg.SeriesID) seriesName := "" if series != nil { seriesName = series.SeriesName } allocationMap := map[uint]*model.ShopSeriesAllocation{pkg.SeriesID: seriesAllocation} packageOverride, _ := s.packageAllocationStore.GetByShopAndPackage(ctx, shopID, packageID) overrideMap := make(map[uint]*model.ShopPackageAllocation) if packageOverride != nil { overrideMap[packageID] = packageOverride } costPrice, priceSource := s.GetCostPrice(ctx, shopID, pkg, allocationMap, overrideMap) return &dto.MyPackageDetailResponse{ ID: pkg.ID, PackageCode: pkg.PackageCode, PackageName: pkg.PackageName, PackageType: pkg.PackageType, Description: "", SeriesID: pkg.SeriesID, SeriesName: seriesName, CostPrice: costPrice, SuggestedRetailPrice: pkg.SuggestedRetailPrice, ProfitMargin: pkg.SuggestedRetailPrice - costPrice, PriceSource: priceSource, Status: pkg.Status, ShelfStatus: pkg.ShelfStatus, }, nil } func (s *Service) ListMySeriesAllocations(ctx context.Context, req *dto.MySeriesAllocationListRequest) ([]*dto.MySeriesAllocationResponse, int64, error) { shopID := middleware.GetShopIDFromContext(ctx) if shopID == 0 { return nil, 0, errors.New(errors.CodeUnauthorized, "当前用户不属于任何店铺") } allocations, err := s.seriesAllocationStore.GetByShopID(ctx, shopID) if err != nil { return nil, 0, fmt.Errorf("获取系列分配失败: %w", err) } total := int64(len(allocations)) page := req.Page pageSize := req.PageSize if page == 0 { page = 1 } if pageSize == 0 { pageSize = constants.DefaultPageSize } start := (page - 1) * pageSize end := start + pageSize if start >= int(total) { return []*dto.MySeriesAllocationResponse{}, total, nil } if end > int(total) { end = int(total) } allocations = allocations[start:end] responses := make([]*dto.MySeriesAllocationResponse, len(allocations)) for i, a := range allocations { series, _ := s.packageSeriesStore.GetByID(ctx, a.SeriesID) seriesCode := "" seriesName := "" if series != nil { seriesCode = series.SeriesCode seriesName = series.SeriesName } allocatorShop, _ := s.shopStore.GetByID(ctx, a.AllocatorShopID) allocatorShopName := "" if allocatorShop != nil { allocatorShopName = allocatorShop.ShopName } availableCount := 0 filters := map[string]interface{}{ "series_id": a.SeriesID, "status": constants.StatusEnabled, "shelf_status": 1, } packages, _, _ := s.packageStore.List(ctx, &store.QueryOptions{Page: 1, PageSize: 1000}, filters) availableCount = len(packages) responses[i] = &dto.MySeriesAllocationResponse{ ID: a.ID, SeriesID: a.SeriesID, SeriesCode: seriesCode, SeriesName: seriesName, PricingMode: a.PricingMode, PricingValue: a.PricingValue, AvailablePackageCount: availableCount, AllocatorShopName: allocatorShopName, Status: a.Status, } } return responses, total, nil } func (s *Service) GetCostPrice(ctx context.Context, shopID uint, pkg *model.Package, allocationMap map[uint]*model.ShopSeriesAllocation, overrideMap map[uint]*model.ShopPackageAllocation) (int64, string) { if override, ok := overrideMap[pkg.ID]; ok && override.Status == constants.StatusEnabled { return override.CostPrice, dto.PriceSourcePackageOverride } allocation, ok := allocationMap[pkg.SeriesID] if !ok { return 0, "" } parentCostPrice := s.getParentCostPriceRecursive(ctx, allocation.AllocatorShopID, pkg) costPrice := s.calculateCostPrice(parentCostPrice, allocation.PricingMode, allocation.PricingValue) return costPrice, dto.PriceSourceSeriesPricing } func (s *Service) getParentCostPriceRecursive(ctx context.Context, shopID uint, pkg *model.Package) int64 { shop, err := s.shopStore.GetByID(ctx, shopID) if err != nil { return pkg.SuggestedCostPrice } if shop.ParentID == nil || *shop.ParentID == 0 { return pkg.SuggestedCostPrice } allocation, err := s.seriesAllocationStore.GetByShopAndSeries(ctx, shopID, pkg.SeriesID) if err != nil { return pkg.SuggestedCostPrice } parentCostPrice := s.getParentCostPriceRecursive(ctx, allocation.AllocatorShopID, pkg) return s.calculateCostPrice(parentCostPrice, allocation.PricingMode, allocation.PricingValue) } func (s *Service) calculateCostPrice(parentCostPrice int64, pricingMode string, pricingValue int64) int64 { switch pricingMode { case model.PricingModeFixed: return parentCostPrice + pricingValue case model.PricingModePercent: return parentCostPrice + (parentCostPrice * pricingValue / 1000) default: return parentCostPrice } }