codeword/internal/services/promocode_service.go
2024-04-09 22:03:20 +03:00

271 lines
8.9 KiB
Go

package services
import (
"codeword/internal/kafka/tariff"
"codeword/internal/models"
"codeword/internal/proto/discount"
"codeword/internal/repository"
"codeword/internal/utils/genID"
"context"
"errors"
"fmt"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.uber.org/zap"
"time"
)
type PromoCodeRepository interface {
CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error)
EditPromoCode(ctx context.Context, req *models.ReqEditPromoCode) (*models.PromoCode, error)
GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error)
ActivatePromo(ctx context.Context, req *models.ActivateReq) (*models.PromoCode, error)
DeletePromoCode(ctx context.Context, promoCodeID string) error
GetPromoCodeByID(ctx context.Context, promoCodeID primitive.ObjectID) (*models.PromoCode, error)
AddFastLink(ctx context.Context, promoCodeID primitive.ObjectID, xid string) error
IncreaseActivationCount(ctx context.Context, promoCodeID primitive.ObjectID) error
}
type PromoStatsRepository interface {
UpdateStatistics(ctx context.Context, req *models.ActivateReq, promoCode *models.PromoCode, userID string) error
GetStatistics(ctx context.Context, promoCodeID string) (models.PromoCodeStats, error)
}
type PromoDeps struct {
Logger *zap.Logger
PromoCodeRepo PromoCodeRepository
StatsRepo PromoStatsRepository
Kafka *tariff.Producer
DiscountClient discount.DiscountServiceClient
}
type PromoCodeService struct {
logger *zap.Logger
promoCodeRepo PromoCodeRepository
statsRepo PromoStatsRepository
kafka *tariff.Producer
discountClient discount.DiscountServiceClient
}
func NewPromoCodeService(deps PromoDeps) *PromoCodeService {
return &PromoCodeService{
logger: deps.Logger,
promoCodeRepo: deps.PromoCodeRepo,
statsRepo: deps.StatsRepo,
kafka: deps.Kafka,
discountClient: deps.DiscountClient,
}
}
func (s *PromoCodeService) CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) {
promoCode, err := s.promoCodeRepo.CreatePromoCode(ctx, req)
if err != nil {
s.logger.Error("Failed to add promocode in database", zap.Error(err))
return nil, err
}
return promoCode, nil
}
func (s *PromoCodeService) EditPromoCode(ctx context.Context, req *models.ReqEditPromoCode) (*models.PromoCode, error) {
editedPromoCode, err := s.promoCodeRepo.EditPromoCode(ctx, req)
if err != nil {
s.logger.Error("Failed to edit promocode in database", zap.Error(err))
return nil, err
}
return editedPromoCode, nil
}
func (s *PromoCodeService) GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error) {
promoCodes, count, err := s.promoCodeRepo.GetPromoCodesList(ctx, req)
if err != nil {
s.logger.Error("Failed to get list promocodes from database", zap.Error(err))
return nil, 0, err
}
return promoCodes, count, nil
}
// todo одумать еще реализацию этого дела, надо уточнить как разделяется ответственность в бонусе между привилегией и скидкой
// разделяется ли она или они всегда вместе, если разделяются то что-то из этого может быть пустым либо все заполеннное,
// соответсвенно надо сделать соответствующие проверки до записи в кафку и до отправки в дискаунт сервис
func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.ActivateReq, userID string) (string, error) {
promoCode, err := s.promoCodeRepo.ActivatePromo(ctx, req)
fmt.Println("SKER20", err, promoCode)
if err != nil {
s.logger.Error("Failed to activate promocode", zap.Error(err))
return "", err
}
//todo такая реализация проверок кажется довольно массивной, думаю как то это стоит сделать параллельно обхаживая все условия
if promoCode.DueTo < time.Now().Unix() && promoCode.DueTo > 0 && promoCode.ActivationLimit != 0 {
err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID)
fmt.Println("SKER21", err)
if err != nil {
return "", err
}
return "", fmt.Errorf("%w: expired on %s", repository.ErrPromoCodeExpired, time.Unix(promoCode.DueTo, 0).Format(time.RFC3339))
}
if promoCode.DueTo == 0 && promoCode.ActivationCount < 0 && promoCode.ActivationLimit != 0 {
err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID)
fmt.Println("SKER22", err)
if err != nil {
return "", err
}
return "", repository.ErrPromoCodeExhausted
}
err = s.statsRepo.UpdateStatistics(ctx, req, promoCode, userID)
fmt.Println("SKER23", err)
if err != nil {
if errors.Is(err, repository.ErrPromoCodeAlreadyActivated) {
err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID)
if err != nil {
return "", err
}
return "", repository.ErrPromoCodeAlreadyActivated
}
s.logger.Error("Failed add in stats", zap.Error(err))
return "", err
}
var postfix string
if req.FastLink != "" {
postfix = fmt.Sprintf(":(%s)", req.FastLink)
}
if promoCode.Bonus.Privilege.PrivilegeID != "" {
var privileges []models.Privilege
privilege := models.Privilege{
PrivilegeID: promoCode.Bonus.Privilege.PrivilegeID,
Amount: promoCode.Bonus.Privilege.Amount,
ServiceKey: promoCode.Bonus.Privilege.ServiceKey,
}
privileges = append(privileges, privilege)
fakeTariff := &models.Tariff{
Name: promoCode.Codeword + postfix,
Privileges: privileges,
Deleted: promoCode.Delete,
CreatedAt: promoCode.CreatedAt,
}
fmt.Println("SKER24", err)
if err := s.kafka.Send(ctx, userID, fakeTariff); err != nil {
s.logger.Error("Failed to send fake tariff to Kafka", zap.Error(err))
return "", err
}
}
if promoCode.Bonus.Discount.Factor != 0 {
disOverHelm := true
emptyString := ""
zero := uint64(0)
discountRequest := &discount.CreateDiscountRequest{
Name: promoCode.Codeword + postfix,
Layer: promoCode.Bonus.Discount.Layer,
Description: "",
Condition: &discount.DiscountCondition{
Coupon: &promoCode.Codeword,
User: &userID,
Group: &promoCode.Bonus.Discount.Target,
Product: &promoCode.Bonus.Discount.Target,
UserType: &emptyString,
PurchasesAmount: &zero,
CartPurchasesAmount: &zero,
Term: &zero,
Usage: &zero,
PriceFrom: &zero,
},
}
if promoCode.Bonus.Discount.Layer == 1 {
discountRequest.Target = &discount.DiscountCalculationTarget{
Products: []*discount.ProductTarget{{
ID: promoCode.Bonus.Discount.Target,
Factor: promoCode.Bonus.Discount.Factor,
}},
Overhelm: &disOverHelm,
}
}
if promoCode.Bonus.Discount.Layer == 2 {
discountRequest.Target = &discount.DiscountCalculationTarget{
TargetGroup: &promoCode.Bonus.Discount.Target,
Factor: promoCode.Bonus.Discount.Factor,
Overhelm: &disOverHelm,
}
}
_, err = s.discountClient.CreateDiscount(ctx, discountRequest)
if err != nil {
s.logger.Error("Failed to create discount", zap.Error(err))
return "", err
}
}
return promoCode.Greetings, nil
}
func (s *PromoCodeService) DeletePromoCode(ctx context.Context, promoCodeID string) error {
err := s.promoCodeRepo.DeletePromoCode(ctx, promoCodeID)
if err != nil {
s.logger.Error("Failed simple delete promocode from database", zap.Error(err))
return err
}
return nil
}
func (s *PromoCodeService) CreateFastLink(ctx context.Context, promoCodeID string) (string, error) {
xid := genID.GenerateXID()
promoID, err := primitive.ObjectIDFromHex(promoCodeID)
if err != nil {
s.logger.Error("Failed conversion promoCodeID to ObjectID", zap.Error(err))
return "", err
}
err = s.promoCodeRepo.AddFastLink(ctx, promoID, xid)
if err != nil {
s.logger.Error("Failed to add fastlink for promocode by promocode id", zap.Error(err))
return "", err
}
return xid, nil
}
func (s *PromoCodeService) GetStats(ctx context.Context, req models.PromoStatReq) (models.PromoCodeStatsResp, error) {
promoStats, err := s.statsRepo.GetStatistics(ctx, req.PromoCodeID)
if err != nil {
s.logger.Error("Failed getting promo stats", zap.Error(err))
return models.PromoCodeStatsResp{}, err
}
var resp models.PromoCodeStatsResp
stats := make(map[string]int)
for key, usageCount := range promoStats.UsageMap {
count := 0
for _, usage := range usageCount {
if (req.From == 0 || usage.Time >= req.From) && (req.To == 0 || usage.Time <= req.To) {
count++
}
}
stats[key] = count
}
totalUsageCount := 0
for _, count := range stats {
totalUsageCount += count
}
resp.UsageMap = stats
resp.UsageCount = totalUsageCount
resp.ID = req.PromoCodeID
return resp, nil
}