This commit is contained in:
skeris 2024-04-14 12:05:53 +03:00
parent 7e6e7e95cf
commit ec5ccc6d11
3 changed files with 506 additions and 12 deletions

483
1 Normal file

@ -0,0 +1,483 @@
package repository
import (
"context"
"time"
"fmt"
"github.com/sirupsen/logrus"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readconcern"
"go.mongodb.org/mongo-driver/mongo/writeconcern"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression/discount"
mongoWrapper "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/mongo"
)
type DiscountRepository struct {
mongoDB *mongo.Collection
logger *logrus.Logger
}
func NewDiscountRepository(mongoDB *mongo.Collection, logger *logrus.Logger) *DiscountRepository {
return &DiscountRepository{
mongoDB: mongoDB,
logger: logger,
}
}
func (receiver *DiscountRepository) getLayer(ctx context.Context, keyGroup, keyParam string, layer uint64, groupPrices, privilegeAmounts map[string]uint64, conditions *core.DiscountConditions) ([]models.Discount, error) {
conditionFilter := discount.ConditionFilter(conditions, groupPrices, privilegeAmounts, layer)
if conditionFilter == nil {
return []models.Discount{}, ErrEmptyArgs
}
filter := bson.M{
"$and": []bson.M{
*conditionFilter,
{"$or": []bson.M{{fields.Discount.Deprecated: false}, {fields.Discount.Deprecated: nil}}},
{"$or": []bson.M{{fields.Audit.Deleted: false}, {fields.Audit.Deleted: nil}}},
},
}
receiver.logger.Info("DR cf", conditionFilter)
cursor, err := receiver.mongoDB.Aggregate(ctx, mongo.Pipeline{
bson.D{
{Key: "$match", Value: filter},
},
bson.D{
{Key: "$sort", Value: bson.M{
keyGroup: 1,
keyParam: -1,
}},
},
bson.D{
{Key: "$group", Value: bson.M{
"_id": "$" + keyGroup,
"doc": bson.M{"$first": "$$ROOT"},
}},
},
bson.D{
{Key: "$replaceRoot", Value: bson.M{
"newRoot": "$doc",
}},
},
})
if err != nil {
return []models.Discount{}, err
}
discounts := []models.Discount{}
err = cursor.All(ctx, &discounts)
fmt.Println("DISCOOOO", discounts)
return discounts, err
}
func (receiver *DiscountRepository) Determine(ctx context.Context, conditions *core.DiscountConditions) ([]models.Discount, uint64, error) {
if conditions == nil {
receiver.logger.Errorln("empty condition on <Determine> of <DiscountRepository>")
return []models.Discount{}, uint64(0), ErrEmptyArgs
}
var (
applyed = []models.Discount{}
keyGroup = "condition.product"
keyParam = "condition.term"
//rawPrice = uint64(0)
groupPrices = map[string]uint64{}
privilegeAmounts = map[string]uint64{}
privilegePrices = map[string]uint64{}
privilegeGroups = map[string]string{}
priority = make(map[string]struct{})
noAccept = make(map[string]struct{})
)
for layer := uint64(1); layer < 5; layer++ {
switch layer {
case 1:
for _, condition := range conditions.Optionals {
if condition.Product == nil || condition.Term == nil {
continue
}
// вычисляем количество привилегий во всей корзине
fmt.Println("GRP", *condition.Group)
privilegeAmounts[*condition.Product] += *condition.Term
privilegePrices[*condition.Product] += *condition.PriceFrom
if _, ok := privilegeAmounts[*condition.Product]; !ok {
privilegeGroups[*condition.Product] = *condition.Group
}
}
discounts, err := receiver.getLayer(ctx, keyGroup, keyParam, layer, privilegeAmounts, groupPrices, conditions)
if err != nil {
return []models.Discount{}, uint64(0), err
}
// проверяем юзера, если его тип nko, то нужно просто применить скидку на nko к сумму сумм привилегий
// в getLayer возвращаются все подходящие скидыки для юзера но нужно только nko
if conditions.Common.UserType == "nko" {
for _, discount := range discounts {
if discount.Condition.UserType != nil && *discount.Condition.UserType == "nko" {
var nkoPrice uint64
for _, privilegePrice := range privilegePrices {
nkoPrice += privilegePrice
}
return []models.Discount{discount}, uint64(float64(nkoPrice) * discount.Target.Factor), nil
}
}
}
// если юзер не nko, то выбираем все скидки за первый слой
// сначала делаем все нужные проверки записываем запреты
for privilege, privilegePrice := range privilegePrices {
fmt.Println("PREPPRIV", privilege, privilegePrice)
// проверки существования скидки за 2 слой noAccept не применям скидки к этой группе если есть такие
for _, discount := range discounts {
if discount.Condition.User != nil && *discount.Condition.User == conditions.Common.User && privilegeGroups[privilege] == *discount.Condition.Group && discount.Layer == 2 {
noAccept[privilegeGroups[privilege]] = struct{}{}
}
}
// если к этой привилегии не было запрета в виде слоя 2, и это скидка для этого юзера и слой 1
// то применям к этой привилегии скидку только раз, но никак не ограничиваем скидку на группу,
// только конкретно на привилегию
for _, discount := range discounts {
fmt.Println("PREPPRIV1", groupPrices, *discount.Condition.User, conditions.Common.User, privilege, *discount.Condition.Product, discount.Layer )
if _, ok := noAccept[privilegeGroups[privilege]]; !ok {
fmt.Println("PREPPRIV3", priority, privilegeGroups)
if discount.Condition.User != nil && discount.Condition.Product != nil && privilege == *discount.Condition.Product && discount.Layer == 1 {
fmt.Println("PREPPRIV4", priority, float64(privilegePrice), discount.Target.Products[0].Factor)
groupPrices[privilegeGroups[privilege]] += uint64(float64(privilegePrice) * discount.Target.Products[0].Factor)
applyed = append(applyed, discount)
priority[privilege] = struct{}{}
}
}
}
}
for privilege, privilegePrice := range privilegePrices {
found := false
// приминеям оставшиеся просто где слой 1 и дозаполняем полностью groupPrices
for _, discount := range discounts {
if _, ok := noAccept[privilegeGroups[privilege]]; !ok {
if _, ok := priority[privilege]; !ok {
if discount.Condition.Product != nil && privilege == *discount.Condition.Product && discount.Layer == 1 && discount.Condition.User == nil {
fmt.Println("PREPPRIV5", groupPrices)
groupPrices[privilegeGroups[privilege]] += uint64(float64(privilegePrice) * discount.Target.Factor)
applyed = append(applyed, discount)
found = true
// сли у нас в ревесте есть такая привилегия но ее нет в пришедших скидках, просто добавляем ее в groupPrices с ключем группы
}
}
}
}
fmt.Println("PREPPRIV2", groupPrices)
if !found {
groupPrices[privilegeGroups[privilege]] += privilegePrice
}
}
keyGroup = "condition.group"
keyParam = "condition.priceFrom"
case 2:
discounts, err := receiver.getLayer(ctx, keyGroup, keyParam, layer, privilegeAmounts, groupPrices, conditions)
if err != nil {
return []models.Discount{}, uint64(0), err
}
var price uint64
// проходимся по мапе где ключ группа и значение прайс за группу с учетом скидки за привилегии
// ищем в скидках скидку за эту группу, для этого юзера, за 2 слой, если найдена то применяем
// и добавляем группу в мапу для предотвращения повторного применения скидки за группу
noAccept := make(map[string]struct{})
for group, nowPrice := range groupPrices {
for _, discount := range discounts {
if discount.Condition.Group != nil && *discount.Condition.Group == group && discount.Layer == 2 && discount.Condition.User != nil && *discount.Condition.User == conditions.Common.User {
groupPrices[group] += uint64(float64(nowPrice) * discount.Target.Factor)
noAccept[group] = struct{}{}
}
}
}
// вычисляем оставшиеся скидки за сервис с ограничениями noAccept
for group, nowPrice := range groupPrices {
for _, discount := range discounts {
if discount.Condition.Group != nil && *discount.Condition.Group == group && discount.Layer == 2 {
if _, ok := noAccept[*discount.Condition.Group]; !ok {
groupPrices[group] += uint64(float64(nowPrice) * discount.Target.Factor)
applyed = append(applyed, discount)
}
}
}
}
// вычислям сумму корзины за 1 и 2 слой для 3 слоя
for _, val := range groupPrices {
price += val
}
fmt.Println("GPP", conditions.Common.CartPurchasesAmount)
conditions.Common.CartPurchasesAmount += price
// если сумма корзины на этом этапе равна 0, то лен(groupPrices) =0 следовательно 3,4 слои тоже будут без скидок
// заполним groupPrices и почитаем conditions.Common.CartPurchasesAmount
if conditions.Common.CartPurchasesAmount == 0 {
for pr, p := range privilegePrices {
conditions.Common.CartPurchasesAmount += p
groupPrices[privilegeGroups[pr]] += p
}
}
//restrictions = noAccept
keyGroup = "condition.user"
keyParam = "condition.cartPurchasesAmount"
case 3:
discounts, err := receiver.getLayer(ctx, keyGroup, keyParam, layer, privilegeAmounts, groupPrices, conditions)
if err != nil {
return []models.Discount{}, uint64(0), err
}
for _, discount := range discounts {
if discount.Condition.User != nil && *discount.Condition.User == "" && discount.Layer == 3 {
for gr, pr := range groupPrices {
if _, ok := noAccept[gr]; !ok {
groupPrices[gr] = uint64(float64(pr) * discount.Target.Factor)
}
}
applyed = append(applyed, discount)
}
}
keyGroup = "condition.userType"
keyParam = "condition.purchasesAmount"
case 4:
discounts, err := receiver.getLayer(ctx, keyGroup, keyParam, layer, privilegeAmounts, groupPrices, conditions)
if err != nil {
return []models.Discount{}, uint64(0), err
}
for _, discount := range discounts {
if discount.Condition.User != nil && *discount.Condition.User == "" && discount.Layer == 4 {
for gr, pr := range groupPrices {
if _, ok := noAccept[gr]; !ok {
groupPrices[gr] = uint64(float64(pr) * discount.Target.Factor)
}
}
applyed = append(applyed, discount)
}
}
}
}
var finishPrice uint64
for _, p := range groupPrices {
finishPrice += p
}
return applyed, finishPrice, nil
}
func (receiver *DiscountRepository) FindByID(ctx context.Context, id string) (*models.Discount, error) {
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
receiver.logger.Errorf("failed to parse ObjectID <%s> on <FindByID> of <DiscountRepository>: %v", id, err)
return nil, ErrInvalidID
}
filter := bson.M{
fields.Discount.ID: objectID,
fields.Discount.Deprecated: false,
fields.Audit.Deleted: false,
}
discount, err := mongoWrapper.FindOne[models.Discount](ctx, &mongoWrapper.RequestSettings{
Driver: receiver.mongoDB,
Filter: filter,
})
if err != nil {
receiver.logger.Errorf("failed to find discount <%s> on <FindByID> of <DiscountRepository>: %v", id, err)
if err == mongo.ErrNoDocuments {
return nil, ErrNoRecord
}
return nil, ErrFindRecord
}
return discount, nil
}
func (receiver *DiscountRepository) FindByUserID(ctx context.Context, userID string) ([]models.Discount, error) {
filter := bson.M{
"$or": expression.GetValueConditionBSON(fields.DiscountCondition.User, userID),
fields.Discount.Deprecated: false,
fields.Audit.Deleted: false,
}
discounts, err := mongoWrapper.Find[models.Discount](ctx, &mongoWrapper.RequestSettings{
Driver: receiver.mongoDB,
Filter: filter,
})
if err != nil {
receiver.logger.Errorf("failed to find user <%s> discount on <FindByUserID> of <DiscountRepository>: %v", userID, err)
return nil, ErrFindRecord
}
return discounts, nil
}
func (receiver *DiscountRepository) FindAll(ctx context.Context) ([]models.Discount, error) {
filter := bson.M{
fields.Audit.Deleted: false,
fields.Discount.Deprecated: false,
}
discounts, err := mongoWrapper.Find[models.Discount](ctx, &mongoWrapper.RequestSettings{
Driver: receiver.mongoDB,
Filter: filter,
})
if err != nil {
receiver.logger.Errorf("failed to find all discount on <FindAll> of <DiscountRepository>: %v", err)
return nil, ErrFindRecord
}
return discounts, nil
}
func (receiver *DiscountRepository) UpdateOne(ctx context.Context, request *core.UpdateDiscountSettings) (*models.Discount, error) {
if request == nil {
receiver.logger.Errorln("empy update on <UpdateOne> of <DiscountRepository>")
return nil, ErrEmptyArgs
}
update := discount.GetUpdateDiscountExpression(request)
objectID, err := primitive.ObjectIDFromHex(update.ID)
if err != nil {
receiver.logger.Errorf("failed to parse ObjectID <%s> on <UpdateOne> of <DiscountRepository>: %v", update.ID, err)
return nil, ErrInvalidID
}
updatedDiscount := models.Discount{}
options := options.FindOneAndUpdate().SetUpsert(false).SetReturnDocument(options.After)
filter := bson.M{
fields.Discount.ID: objectID,
}
if err := receiver.mongoDB.FindOneAndUpdate(ctx, filter, update.Expression, options).Decode(&updatedDiscount); err != nil {
receiver.logger.Errorf("failed to update discount with id <%s> on <UpdateOne> of <DiscountRepository>: %v", update.ID, err)
if err == mongo.ErrNoDocuments {
return nil, ErrNoRecord
}
return nil, ErrUpdateRecord
}
return &updatedDiscount, nil
}
func (receiver *DiscountRepository) UpdateMany(ctx context.Context, updates []core.UpdateDiscountSettings) error {
if len(updates) < 1 {
receiver.logger.Errorln("empy updates on <UpdateMany>")
return ErrEmptyArgs
}
transactionOptions := options.Transaction().
SetReadConcern(readconcern.Snapshot()).
SetWriteConcern(writeconcern.New(writeconcern.WMajority()))
session, err := receiver.mongoDB.Database().Client().StartSession()
if err != nil {
receiver.logger.Errorln("failed to start session on <UpdateMany> of <DiscountRepository>")
return ErrTransactionSessionStart
}
defer session.EndSession(ctx)
if _, transactionError := session.WithTransaction(ctx, func(sessionContext mongo.SessionContext) (interface{}, error) {
for _, update := range updates {
updateCopy := update
if _, err := receiver.UpdateOne(sessionContext, &updateCopy); err != nil {
receiver.logger.Errorf("failed to update <%s> in transaction on <UpdateMany> of <DiscountRepository>: %v", update.ID, err)
return nil, err
}
}
return nil, nil
}, transactionOptions); transactionError != nil {
receiver.logger.Errorf("failed to transaction on <UpdateMany> of <DiscountRepository>: %v", transactionError)
return ErrTransaction
}
return nil
}
func (receiver *DiscountRepository) Insert(ctx context.Context, discount *models.Discount) (string, error) {
if discount.Condition.User != nil && *discount.Condition.User != "" {
filter := bson.M{"condition.user": *discount.Condition.User, "audit.deleted": false}
update := bson.M{
"$set": bson.M{"audit.deleted": true},
}
_, err := receiver.mongoDB.UpdateMany(ctx, filter, update)
if err != nil && err != mongo.ErrNoDocuments {
return "", ErrAlreadyExistDiscount
}
}
res, err := receiver.mongoDB.InsertOne(ctx, discount.Sanitize())
if err != nil {
receiver.logger.Errorf("failed to insert record on <Insert> of <DiscountRepository>: %v", err)
return "", ErrInsertRecord
}
return res.InsertedID.(primitive.ObjectID).Hex(), nil
}
func (receiver *DiscountRepository) DeleteByID(ctx context.Context, id string) (*models.Discount, error) {
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
receiver.logger.Errorf("failed to parse ObjectID <%s> on <DeleteByID> of <DiscountRepository>: %v", id, err)
return nil, ErrInvalidID
}
discount := models.Discount{}
now := time.Now()
options := options.FindOneAndUpdate().SetReturnDocument(options.After)
update := bson.M{"$set": bson.M{
fields.Audit.Deleted: true,
fields.Audit.DeletedAt: now,
fields.Audit.UpdatedAt: now,
}}
filter := bson.M{
fields.Discount.ID: objectID,
}
if err := receiver.mongoDB.FindOneAndUpdate(ctx, filter, update, options).Decode(&discount); err != nil {
receiver.logger.Errorf("failed to set 'deleted=true' with id <%s> on <DeleteByID> of <DiscountRepository>: %v", id, err)
if err == mongo.ErrNoDocuments {
return nil, ErrNoRecord
}
return nil, ErrUpdateRecord
}
return &discount, nil
}

@ -47,8 +47,6 @@ func (receiver *DiscountRepository) getLayer(ctx context.Context, keyGroup, keyP
},
}
receiver.logger.Info("DR cf", conditionFilter)
cursor, err := receiver.mongoDB.Aggregate(ctx, mongo.Pipeline{
bson.D{
{Key: "$match", Value: filter},
@ -56,6 +54,7 @@ func (receiver *DiscountRepository) getLayer(ctx context.Context, keyGroup, keyP
bson.D{
{Key: "$sort", Value: bson.M{
keyGroup: 1,
"target.overhelm": -1,
keyParam: -1,
}},
},
@ -111,11 +110,10 @@ func (receiver *DiscountRepository) Determine(ctx context.Context, conditions *c
continue
}
// вычисляем количество привилегий во всей корзине
fmt.Println("GRP", *condition.Group)
privilegeAmounts[*condition.Product] += *condition.Term
privilegePrices[*condition.Product] += *condition.PriceFrom
if _, ok := privilegeAmounts[*condition.Product]; !ok {
privilegeGroups[*condition.Product] = *condition.Group
}
privilegeGroups[*condition.Product] = *condition.Group
}
discounts, err := receiver.getLayer(ctx, keyGroup, keyParam, layer, privilegeAmounts, groupPrices, conditions)
@ -139,6 +137,7 @@ func (receiver *DiscountRepository) Determine(ctx context.Context, conditions *c
// сначала делаем все нужные проверки записываем запреты
for privilege, privilegePrice := range privilegePrices {
fmt.Println("PREPPRIV", privilege, privilegePrice)
// проверки существования скидки за 2 слой noAccept не применям скидки к этой группе если есть такие
for _, discount := range discounts {
@ -151,14 +150,16 @@ func (receiver *DiscountRepository) Determine(ctx context.Context, conditions *c
// только конкретно на привилегию
for _, discount := range discounts {
if _, ok := noAccept[privilegeGroups[privilege]]; !ok {
if discount.Condition.User != nil && *discount.Condition.User == conditions.Common.User && discount.Condition.Product != nil && privilege == *discount.Condition.Product && discount.Layer == 1 {
groupPrices[privilegeGroups[privilege]] += uint64(float64(privilegePrice) * discount.Target.Factor)
if discount.Target.Overhelm && discount.Condition.Product != nil && privilege == *discount.Condition.Product && discount.Layer == 1 {
fmt.Println("PREPPRIV1", groupPrices, *discount.Condition.User, conditions.Common.User, privilege, *discount.Condition.Product, discount.Layer )
groupPrices[privilegeGroups[privilege]] += uint64(float64(privilegePrice) * discount.Target.Products[0].Factor)
applyed = append(applyed, discount)
priority[privilege] = struct{}{}
}
}
}
}
fmt.Println("PREPPRIV3", groupPrices)
for privilege, privilegePrice := range privilegePrices {
found := false
@ -166,37 +167,43 @@ func (receiver *DiscountRepository) Determine(ctx context.Context, conditions *c
for _, discount := range discounts {
if _, ok := noAccept[privilegeGroups[privilege]]; !ok {
if _, ok := priority[privilege]; !ok {
if discount.Condition.Product != nil && privilege == *discount.Condition.Product && discount.Layer == 1 {
if discount.Condition.Product != nil && privilege == *discount.Condition.Product && discount.Layer == 1 && *discount.Condition.User == "" {
fmt.Println("PREPPRIV4", groupPrices, privilege)
groupPrices[privilegeGroups[privilege]] += uint64(float64(privilegePrice) * discount.Target.Factor)
applyed = append(applyed, discount)
found = true
// сли у нас в ревесте есть такая привилегия но ее нет в пришедших скидках, просто добавляем ее в groupPrices с ключем группы
}
} else {
found = true
}
}
}
if !found {
fmt.Println("PREPPRIV5", groupPrices, privilege,privilegePrice)
groupPrices[privilegeGroups[privilege]] += privilegePrice
}
fmt.Println("PREPPRIV2", groupPrices, privilege, privilegePrice)
}
keyGroup = "condition.group"
keyParam = "condition.priceFrom"
case 2:
fmt.Println("PV4", groupPrices)
discounts, err := receiver.getLayer(ctx, keyGroup, keyParam, layer, privilegeAmounts, groupPrices, conditions)
if err != nil {
return []models.Discount{}, uint64(0), err
}
}
var price uint64
// проходимся по мапе где ключ группа и значение прайс за группу с учетом скидки за привилегии
// ищем в скидках скидку за эту группу, для этого юзера, за 2 слой, если найдена то применяем
// и добавляем группу в мапу для предотвращения повторного применения скидки за группу
noAccept := make(map[string]struct{})
for group, nowPrice := range groupPrices {
for _, discount := range discounts {
if discount.Condition.Group != nil && *discount.Condition.Group == group && discount.Layer == 2 && discount.Condition.User != nil && *discount.Condition.User == conditions.Common.User {
fmt.Println("PV3", group, nowPrice, conditions.Common.User, *discount.Condition.User)
if discount.Condition.Group != nil && *discount.Condition.Group == group && discount.Layer == 2 && *discount.Condition.User == conditions.Common.User {
groupPrices[group] += uint64(float64(nowPrice) * discount.Target.Factor)
noAccept[group] = struct{}{}
}
@ -205,6 +212,7 @@ func (receiver *DiscountRepository) Determine(ctx context.Context, conditions *c
// вычисляем оставшиеся скидки за сервис с ограничениями noAccept
for group, nowPrice := range groupPrices {
for _, discount := range discounts {
fmt.Println("PV2", group, nowPrice, discount)
if discount.Condition.Group != nil && *discount.Condition.Group == group && discount.Layer == 2 {
if _, ok := noAccept[*discount.Condition.Group]; !ok {
groupPrices[group] += uint64(float64(nowPrice) * discount.Target.Factor)
@ -213,13 +221,14 @@ func (receiver *DiscountRepository) Determine(ctx context.Context, conditions *c
}
}
}
fmt.Println("PV2", groupPrices)
// вычислям сумму корзины за 1 и 2 слой для 3 слоя
for _, val := range groupPrices {
price += val
}
fmt.Println("GPP", conditions.Common.CartPurchasesAmount)
fmt.Println("GPP", price, groupPrices, conditions.Common.CartPurchasesAmount)
conditions.Common.CartPurchasesAmount += price
// если сумма корзины на этом этапе равна 0, то лен(groupPrices) =0 следовательно 3,4 слои тоже будут без скидок
// заполним groupPrices и почитаем conditions.Common.CartPurchasesAmount

@ -30,6 +30,7 @@ func ConditionFilter(conditions *core.DiscountConditions, privilegeAmounts, grou
"$and": []bson.M{
{fields.DiscountCondition.Product: product},
{fields.DiscountCondition.Term: bson.M{"$lte": amount}},
{fields.DiscountCondition.User: ""},
},
})
}
@ -48,6 +49,7 @@ func ConditionFilter(conditions *core.DiscountConditions, privilegeAmounts, grou
"$and": []bson.M{
{fields.DiscountCondition.Group: group},
{fields.DiscountCondition.PriceFrom: bson.M{"$lte": price}},
{fields.DiscountCondition.User: ""},
},
})
}