feat: overhelm

This commit is contained in:
Mikhail 2023-07-04 04:04:31 +00:00 committed by Kirill
parent e15242a2b8
commit 01dce42f5c
112 changed files with 9154 additions and 7750 deletions

@ -1,11 +1,11 @@
GRPC_HOST=0.0.0.0
GPRC_PORT=9001
HTTP_PORT=8001
MONGO_HOST=mongo
MONGO_PORT=27017
MONGO_USER=test
MONGO_PASSWORD=test
MONGO_AUTH=admin
GRPC_HOST=0.0.0.0
GPRC_PORT=9001
HTTP_PORT=8001
MONGO_HOST=mongo
MONGO_PORT=27017
MONGO_USER=test
MONGO_PASSWORD=test
MONGO_AUTH=admin
MONGO_DB_NAME=admin

@ -1,8 +1,31 @@
stages:
- lint
- test
- clean
- build
- deploy
lint:
image: golangci/golangci-lint:v1.51-alpine
stage: lint
script:
- go generate ./internal/...
- golangci-lint version
- golangci-lint run --disable-all --enable=vet --enable=golint ./...
test:
image: golang:1.20-alpine
stage: test
coverage: /\(statements\)(?:\s+)?(\d+(?:\.\d+)?%)/
script:
- CGO_ENABLED=0 go test ./internal/... -coverprofile=coverage.out
- go tool cover -html=coverage.out -o coverage.html
- go tool cover -func coverage.out
artifacts:
expire_in: "3 days"
paths:
- coverage.html
clean-old:
stage: clean
image:

164
.golangci.yaml Normal file

@ -0,0 +1,164 @@
run:
timeout: 5m
skip-files:
- \.pb\.go$
- .pb.go
- \.pb\.validate\.go$
- \.pb\.gw\.go$
- .pb.gw.go
- \.gen\.go$
skip-dirs:
- mocks
linters:
disable-all: true
enable:
- asasalint
- asciicheck
- bidichk
- bodyclose
- containedctx
- dogsled
- dupword
- durationcheck
- errcheck
- errchkjson
- exportloopref
- goconst
- gocritic
- godot
- gofmt
- gci
- goprintffuncname
- gosec
- gosimple
- govet
- importas
- ineffassign
- misspell
- nakedret
- nilerr
- noctx
- nolintlint
- nosprintfhostport
- prealloc
- predeclared
- revive
- rowserrcheck
- staticcheck
- stylecheck
- thelper
- typecheck
- unconvert
- unparam
- unused
- usestdlibvars
- whitespace
linters-settings:
errcheck:
exclude-functions:
- (io.Closer).Close
govet:
check-shadowing: true
gci:
custom-order: false
section-separators:
- newLine
sections:
- standard # Standard section: captures all standard packages.
- default # Default section: contains all imports that could not be matched to another section type.
- blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled.
- dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled.
importas:
no-unaliased: true
alias:
# Foundation libraries
- pkg: git.sbercloud.tech/products/paas/shared/foundation/management-server
alias: mgmtserver
maligned:
suggest-new: true
goconst:
min-len: 2
min-occurrences: 2
lll:
line-length: 140
revive:
rules:
# The following rules are recommended https://github.com/mgechev/revive#recommended-configuration
- name: blank-imports
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: error-return
- name: error-strings
- name: error-naming
# - name: exported
- name: if-return
- name: increment-decrement
- name: var-naming
- name: var-declaration
# - name: package-comments
- name: range
- name: receiver-naming
- name: time-naming
- name: unexported-return
- name: indent-error-flow
- name: errorf
- name: empty-block
- name: superfluous-else
- name: unused-parameter
- name: unreachable-code
- name: redefines-builtin-id
#
# Rules in addition to the recommended configuration above.
#
- name: bool-literal-in-expr
- name: constant-logical-expr
gosec:
excludes:
- G307 # Deferring unsafe method "Close" on type "\*os.File"
- G108 # Profiling endpoint is automatically exposed on /debug/pprof
gocritic:
enabled-tags:
- diagnostic
- experimental
- performance
disabled-checks:
- appendAssign
- dupImport # https://github.com/go-critic/go-critic/issues/845
- evalOrder
- ifElseChain
- octalLiteral
- regexpSimplify
- sloppyReassign
- truncateCmp
- typeDefFirst
- unnamedResult
- unnecessaryDefer
- whyNoLint
- wrapperFunc
- rangeValCopy
- hugeParam
issues:
exclude-rules:
- text: "at least one file in a package should have a package comment"
linters:
- stylecheck
- text: "should have a package comment, unless it's in another file for this package"
linters:
- golint
- text: "should have comment or be unexported"
linters:
- golint
- path: _test\.go
linters:
- gosec
- dupl
exclude-use-default: false
output:
# colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number"
format: colored-line-number
print-linter-name: true

6
.mockery.yaml Normal file

@ -0,0 +1,6 @@
exported: True
inpackage: False
keeptree: True
case: underscore
with-expecter: True
inpackage-suffix: True

@ -1,72 +1,72 @@
# BUILD
FROM golang:1.19.5-alpine AS build
# Update depences
RUN apk update && apk add --no-cache curl
# Create build directory
RUN mkdir /app/bin -p
RUN mkdir /bin/golang-migrate -p
# Download migrate app
RUN GOLANG_MIGRATE_VERSION=v4.15.1 && \
curl -L https://github.com/golang-migrate/migrate/releases/download/${GOLANG_MIGRATE_VERSION}/migrate.linux-amd64.tar.gz |\
tar xvz migrate -C /bin/golang-migrate
# Download health check utility
RUN GRPC_HEALTH_PROBE_VERSION=v0.4.6 && \
wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \
chmod +x /bin/grpc_health_probe
# Set home directory
WORKDIR /app
# Copy go.mod
ADD go.mod go.sum /app/
# Download go depences
RUN go mod download
# Copy all local files
ADD . /app
# Build app
RUN GOOS=linux go build -o bin ./...
# TEST
FROM alpine:latest AS test
# Install packages
RUN apk --no-cache add ca-certificates
# Create home directory
WORKDIR /app
# Copy build file
COPY --from=build /app/bin/app ./app
# CMD
CMD ["./app"]
# MIGRATION
FROM alpine:latest AS migration
# Install packages
RUN apk --no-cache add ca-certificates
# Create home directory
WORKDIR /app
# Copy migration dir
COPY --from=build /app/migrations/tests ./migrations
# Install migrate tool
COPY --from=build /bin/golang-migrate /usr/local/bin
# PRODUCTION
FROM alpine:latest AS production
# Install packages
RUN apk --no-cache add ca-certificates
# Create home directory
WORKDIR /app
# Copy build file
COPY --from=build /app/bin/app ./app
# Copy grpc health probe dir
COPY --from=build /bin/grpc_health_probe /bin/grpc_health_probe
# Install migrate tool
COPY --from=build /bin/golang-migrate /usr/local/bin
# CMD
CMD ["./app"]
# BUILD
FROM golang:1.19.5-alpine AS build
# Update depences
RUN apk update && apk add --no-cache curl
# Create build directory
RUN mkdir /app/bin -p
RUN mkdir /bin/golang-migrate -p
# Download migrate app
RUN GOLANG_MIGRATE_VERSION=v4.15.1 && \
curl -L https://github.com/golang-migrate/migrate/releases/download/${GOLANG_MIGRATE_VERSION}/migrate.linux-amd64.tar.gz |\
tar xvz migrate -C /bin/golang-migrate
# Download health check utility
RUN GRPC_HEALTH_PROBE_VERSION=v0.4.6 && \
wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \
chmod +x /bin/grpc_health_probe
# Set home directory
WORKDIR /app
# Copy go.mod
ADD go.mod go.sum /app/
# Download go depences
RUN go mod download
# Copy all local files
ADD . /app
# Build app
RUN GOOS=linux go build -o bin ./...
# TEST
FROM alpine:latest AS test
# Install packages
RUN apk --no-cache add ca-certificates
# Create home directory
WORKDIR /app
# Copy build file
COPY --from=build /app/bin/app ./app
# CMD
CMD ["./app"]
# MIGRATION
FROM alpine:latest AS migration
# Install packages
RUN apk --no-cache add ca-certificates
# Create home directory
WORKDIR /app
# Copy migration dir
COPY --from=build /app/migrations/tests ./migrations
# Install migrate tool
COPY --from=build /bin/golang-migrate /usr/local/bin
# PRODUCTION
FROM alpine:latest AS production
# Install packages
RUN apk --no-cache add ca-certificates
# Create home directory
WORKDIR /app
# Copy build file
COPY --from=build /app/bin/app ./app
# Copy grpc health probe dir
COPY --from=build /bin/grpc_health_probe /bin/grpc_health_probe
# Install migrate tool
COPY --from=build /bin/golang-migrate /usr/local/bin
# CMD
CMD ["./app"]

@ -1,42 +1,42 @@
SERVICE_NAME = discount
help: ## show this help
@echo 'usage: make [target] ...'
@echo ''
@echo 'targets:'
@egrep '^(.+)\:\ .*##\ (.+)' ${MAKEFILE_LIST} | sed 's/:.*##/#/' | column -t -c 2 -s '#'
install: ## install all go dependencies
go install \
github.com/bufbuild/buf/cmd/buf \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \
google.golang.org/grpc/cmd/protoc-gen-go-grpc \
google.golang.org/protobuf/cmd/protoc-gen-go
generate: ## generate grpc proto for golang
buf generate --path ./proto/${SERVICE_NAME}
test: ## run all layers tests
@make test.unit
@make test.integration
test.unit: ## run unit tests
go test ./...
test.integration: ## run integration tests
@make test.integration.up
@make test.integration.start
@make test.integration.down
test.integration.up: ## build integration test environment
docker-compose -f deployments/test/docker-compose.yaml --env-file ./.env.test up -d
test.integration.start: ## run integration test
go test -tags integration ./tests/integration/...
test.integration.down: ## shutting down integration environment
docker-compose -f deployments/test/docker-compose.yaml --env-file ./.env.test down --volumes --rmi local
run: ## run app
SERVICE_NAME = discount
help: ## show this help
@echo 'usage: make [target] ...'
@echo ''
@echo 'targets:'
@egrep '^(.+)\:\ .*##\ (.+)' ${MAKEFILE_LIST} | sed 's/:.*##/#/' | column -t -c 2 -s '#'
install: ## install all go dependencies
go install \
github.com/bufbuild/buf/cmd/buf@v1.23.1 \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest \
google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
google.golang.org/protobuf/cmd/protoc-gen-go@latest
generate: ## generate grpc proto for golang
buf generate --path ./proto/${SERVICE_NAME}
test: ## run all layers tests
@make test.unit
@make test.integration
test.unit: ## run unit tests
go test ./...
test.integration: ## run integration tests
@make test.integration.up
@make test.integration.start
@make test.integration.down
test.integration.up: ## build integration test environment
docker-compose -f deployments/test/docker-compose.yaml --env-file ./.env.test up -d
test.integration.start: ## run integration test
go test -tags integration ./tests/integration/...
test.integration.down: ## shutting down integration environment
docker-compose -f deployments/test/docker-compose.yaml --env-file ./.env.test down --volumes --rmi local
run: ## run app
go run ./cmd/app/main.go

106
README.md

@ -1,53 +1,53 @@
# accruals-service
Микросервис для создания скидок и формирования цен из реализованных скидок
| Branch | Pipeline | Code coverage |
| ------------- |:-----------------:| --------------:|
| main | [![pipeline status](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/main/pipeline.svg)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | [![coverage report](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/main/coverage.svg?job=test)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) |
| staging | [![pipeline status](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/staging/pipeline.svg)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | [![coverage report](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/staging/coverage.svg?job=test)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) |
| dev | [![pipeline status](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/dev/pipeline.svg)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | [![coverage report](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/dev/coverage.svg?job=test)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) |
## Переменные окружения приложения
```
GRPC_HOST - хост прослушивания приложения (gRPC)
GPRC_PORT - порт прослушивания приложения (gRPC)
HTTP_PORT - порт прослушивания приложения (HTTP)
MONGO_HOST - хост базы данных
MONGO_PORT - порт базы данных
MONGO_USER - имя пользователя базы данных для авторизации
MONGO_PASSWORD - пароль пользователя базы данных для авторизации
MONGO_AUTH - название базы данных для авторизации
MONGO_DATABASE_NAME - название базы данных, к которой будет идти подключение
```
## Пример переменных окружения:
```
GRPC_HOST=0.0.0.0
GPRC_PORT=9001
HTTP_PORT=8001
MONGO_HOST=localhost
MONGO_PORT=27017
MONGO_USER=test
MONGO_PASSWORD=test
MONGO_AUTH=admin
```
## Команды для работы с приложением:
```
make help - вывести список доступных команд с описанием
make install - устанавливает все необходимые зависимости и инструменты
make generate - генерирует proto файлы gRPC сервиса
make test - запускает unit и интеграционные тесты
make test.unit - запуск unit тестов
make test.integration - запуск интеграционных тестов (поднятие и завершение окружения)
make test.integration.up - поднятие тестового окружения
make test.integration.start - запуск интеграционных тестов
make test.integration.down - завершение тестового окружения
make run - запуск приложения
```
# accruals-service
Микросервис для создания скидок и формирования цен из реализованных скидок
| Branch | Pipeline | Code coverage |
| ------------- |:-----------------:| --------------:|
| main | [![pipeline status](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/main/pipeline.svg)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | [![coverage report](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/main/coverage.svg?job=test)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) |
| staging | [![pipeline status](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/staging/pipeline.svg)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | [![coverage report](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/staging/coverage.svg?job=test)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) |
| dev | [![pipeline status](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/dev/pipeline.svg)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | [![coverage report](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/dev/coverage.svg?job=test)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) |
## Переменные окружения приложения
```
GRPC_HOST - хост прослушивания приложения (gRPC)
GPRC_PORT - порт прослушивания приложения (gRPC)
HTTP_PORT - порт прослушивания приложения (HTTP)
MONGO_HOST - хост базы данных
MONGO_PORT - порт базы данных
MONGO_USER - имя пользователя базы данных для авторизации
MONGO_PASSWORD - пароль пользователя базы данных для авторизации
MONGO_AUTH - название базы данных для авторизации
MONGO_DATABASE_NAME - название базы данных, к которой будет идти подключение
```
## Пример переменных окружения:
```
GRPC_HOST=0.0.0.0
GPRC_PORT=9001
HTTP_PORT=8001
MONGO_HOST=localhost
MONGO_PORT=27017
MONGO_USER=test
MONGO_PASSWORD=test
MONGO_AUTH=admin
```
## Команды для работы с приложением:
```
make help - вывести список доступных команд с описанием
make install - устанавливает все необходимые зависимости и инструменты
make generate - генерирует proto файлы gRPC сервиса
make test - запускает unit и интеграционные тесты
make test.unit - запуск unit тестов
make test.integration - запуск интеграционных тестов (поднятие и завершение окружения)
make test.integration.up - поднятие тестового окружения
make test.integration.start - запуск интеграционных тестов
make test.integration.down - завершение тестового окружения
make run - запуск приложения
```

@ -1,12 +1,8 @@
version: v1beta1
version: v1
plugins:
- name: go
out: internal/proto
- name: go-grpc
out: internal/proto
opt:
- require_unimplemented_servers=false
- name: grpc-gateway
out: internal/proto
- name: openapiv2
out: internal/proto
- require_unimplemented_servers=false

3
buf.work.yaml Normal file

@ -0,0 +1,3 @@
version: v1
directories:
- proto

@ -1,7 +1,4 @@
version: v1beta1
build:
roots:
- proto
version: v1
lint:
use:
- DEFAULT

@ -8,7 +8,6 @@ import (
formatter "github.com/antonfisher/nested-logrus-formatter"
"github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/app"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/env"

@ -1,46 +1,46 @@
version: "3"
services:
app:
build:
context: ../../.
dockerfile: Dockerfile
target: test
env_file:
- ../../.env.test
ports:
- 9001:9001
- 8001:8001
depends_on:
- migration
networks:
- integration_test
migration:
build:
context: ../../.
dockerfile: Dockerfile
target: migration
command:
[
"sh",
"-c",
'migrate -source file://migrations -database "mongodb://$MONGO_USER:$MONGO_PASSWORD@$MONGO_HOST:$MONGO_PORT/$MONGO_AUTH?authSource=$MONGO_AUTH" up',
]
depends_on:
- mongo
networks:
- integration_test
mongo:
image: 'mongo:6.0.3'
environment:
MONGO_INITDB_ROOT_USERNAME: test
MONGO_INITDB_ROOT_PASSWORD: test
ports:
- '27017:27017'
networks:
- integration_test
networks:
version: "3"
services:
app:
build:
context: ../../.
dockerfile: Dockerfile
target: test
env_file:
- ../../.env.test
ports:
- 9001:9001
- 8001:8001
depends_on:
- migration
networks:
- integration_test
migration:
build:
context: ../../.
dockerfile: Dockerfile
target: migration
command:
[
"sh",
"-c",
'migrate -source file://migrations -database "mongodb://$MONGO_USER:$MONGO_PASSWORD@$MONGO_HOST:$MONGO_PORT/$MONGO_AUTH?authSource=$MONGO_AUTH" up',
]
depends_on:
- mongo
networks:
- integration_test
mongo:
image: 'mongo:6.0.3'
environment:
MONGO_INITDB_ROOT_USERNAME: test
MONGO_INITDB_ROOT_PASSWORD: test
ports:
- '27017:27017'
networks:
- integration_test
networks:
integration_test:

82
go.mod

@ -5,37 +5,89 @@ go 1.20
require (
github.com/antonfisher/nested-logrus-formatter v1.3.1
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0
github.com/joho/godotenv v1.4.0
github.com/sethvargo/go-envconfig v0.8.3
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.1
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
go.mongodb.org/mongo-driver v1.11.1
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
google.golang.org/genproto v0.0.0-20230119192704-9d59e20e5cd1
google.golang.org/grpc v1.52.0
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e
google.golang.org/grpc v1.55.0
google.golang.org/protobuf v1.31.0
)
require (
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/bufbuild/buf v1.23.1 // indirect
github.com/bufbuild/connect-go v1.8.0 // indirect
github.com/bufbuild/connect-opentelemetry-go v0.3.0 // indirect
github.com/bufbuild/protocompile v0.5.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/docker/cli v24.0.2+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v24.0.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/go-chi/chi/v5 v5.0.8 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gofrs/uuid/v5 v5.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.1.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/google/go-containerregistry v0.15.2 // indirect
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 // indirect
github.com/klauspost/compress v1.16.6 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/cors v1.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tetratelabs/wazero v1.2.1 // indirect
github.com/vbatts/tar-split v0.11.3 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/net v0.4.0 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/text v0.5.0 // indirect
go.opentelemetry.io/otel v1.16.0 // indirect
go.opentelemetry.io/otel/metric v1.16.0 // indirect
go.opentelemetry.io/otel/sdk v1.16.0 // indirect
go.opentelemetry.io/otel/trace v1.16.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.11.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/term v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/tools v0.10.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

149
go.sum

@ -1,25 +1,70 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
github.com/bufbuild/buf v1.23.1 h1:BeMkA3+e9XTFEZPDlAI7cydFKlq24wwYiOHp8CvsRzc=
github.com/bufbuild/buf v1.23.1/go.mod h1:ERFRzJiIjAOzUSJ3vz1zoI7XfxlBnCwZEyL+NJm4pko=
github.com/bufbuild/connect-go v1.8.0 h1:srluNkFkZBfSfg9Qb6DrO+5nMaxix//h2ctrHZhMGKc=
github.com/bufbuild/connect-go v1.8.0/go.mod h1:GmMJYR6orFqD0Y6ZgX8pwQ8j9baizDrIQMm1/a6LnHk=
github.com/bufbuild/connect-opentelemetry-go v0.3.0 h1:AuZi3asTDKmjGtd2aqpyP4p5QvBFG/YEaHopViLatnk=
github.com/bufbuild/connect-opentelemetry-go v0.3.0/go.mod h1:r1ppyTtu1EWeRodk4Q/JbyQhIWtO7eR3GoRDzjeEcNU=
github.com/bufbuild/protocompile v0.5.1 h1:mixz5lJX4Hiz4FpqFREJHIXLfaLBntfaJv1h+/jS+Qg=
github.com/bufbuild/protocompile v0.5.1/go.mod h1:G5iLmavmF4NsYtpZFvE3B/zFch2GIY8+wjsYLR/lc40=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/cli v24.0.2+incompatible h1:QdqR7znue1mtkXIJ+ruQMGQhpw2JzMJLRXp6zpzF6tM=
github.com/docker/cli v24.0.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.2+incompatible h1:eATx+oLz9WdNVkQrr0qjQ8HvRJ4bOOxfzEo8R+dA3cg=
github.com/docker/docker v24.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -27,6 +72,8 @@ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@ -34,39 +81,84 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-containerregistry v0.15.2 h1:MMkSh+tjSdnmJZO7ljvEqV1DjfekB6VUEAZgy3a+TQE=
github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 h1:1JYBfzqrWPcCclBwxFCPAou9n+q86mfnu7NAeHfte7A=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0/go.mod h1:YDZoGHuwE+ov0c8smSH49WLF3F2LaWnYYuDVd+EWrc0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 h1:2uT3aivO7NVpUPGcQX7RbHijHMyWix/yCnIrCWc+5co=
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk=
github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8=
github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sethvargo/go-envconfig v0.8.3 h1:dXyUrDCJvCm3ybP7yNpiux93qoSORvuH23bdsgFfiJ0=
github.com/sethvargo/go-envconfig v0.8.3/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@ -79,8 +171,15 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tetratelabs/wazero v1.2.1 h1:J4X2hrGzJvt+wqltuvcSjHQ7ujQxA9gb6PeMs4qlUWs=
github.com/tetratelabs/wazero v1.2.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck=
github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
@ -93,22 +192,42 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8=
go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -120,6 +239,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -129,6 +250,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -137,16 +260,26 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -155,6 +288,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -166,6 +301,12 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20230119192704-9d59e20e5cd1 h1:wSjSSQW7LuPdv3m1IrSN33nVxH/kID6OIKy+FMwGB2k=
google.golang.org/genproto v0.0.0-20230119192704-9d59e20e5cd1/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw=
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk=
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM=
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@ -173,15 +314,23 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk=
google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk=
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -1,53 +1,71 @@
package app
import (
"context"
"time"
"github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/initialize"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/server"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/mongo"
)
func Run(config *core.Config, logger *logrus.Logger) {
ctx, cancel := context.WithCancel(context.Background())
database, err := mongo.Connect(ctx, &mongo.ConnectDeps{
Configuration: &config.Database.Connection,
Timeout: 10 * time.Second,
})
if err != nil {
logger.Fatalln("failed connection to db: ", err)
panic(err)
}
repositories := initialize.NewRepositories(&initialize.RepositoriesDeps{
Logger: logger,
Database: database,
})
services := initialize.NewServices(&initialize.ServicesDeps{
Logger: logger,
Respositories: repositories,
})
controllers := initialize.NewControllers(&initialize.ControllersDeps{
Logger: logger,
Services: services,
})
serverGRPC := server.NewGRPC(logger).Register(controllers.DiscountController)
serverHTTP := server.NewHTTP(logger).Register(ctx, &config.GRPC)
go serverGRPC.Run(&config.GRPC)
go serverHTTP.Run(&config.HTTP)
gracefulShutdown(ctx, &gracefulShutdownDeps{
serverGRPC: serverGRPC,
database: database.Client(),
cancel: cancel,
})
}
package app
import (
"context"
"os/signal"
"syscall"
"time"
"github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/initialize"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/server"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/closer"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/mongo"
)
const (
shutdownTimeout = 5 * time.Second
)
func Run(config *core.Config, logger *logrus.Logger) {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
closer := closer.New()
database, err := mongo.Connect(ctx, &mongo.ConnectDeps{
Configuration: &config.Database.Connection,
Timeout: 10 * time.Second,
})
if err != nil {
logger.Fatalln("failed connection to db: ", err)
panic(err)
}
repositories := initialize.NewRepositories(&initialize.RepositoriesDeps{
Logger: logger,
Database: database,
})
services := initialize.NewServices(&initialize.ServicesDeps{
Logger: logger,
Repositories: repositories,
})
controllers := initialize.NewControllers(&initialize.ControllersDeps{
Logger: logger,
Services: services,
})
serverGRPC := server.NewGRPC(logger).Register(controllers.DiscountController)
serverHTTP := server.NewHTTP(logger).Register(ctx, &config.GRPC)
go serverGRPC.Run(&config.GRPC)
go serverHTTP.Run(&config.HTTP)
closer.Add(database.Client().Disconnect)
closer.Add(serverGRPC.Stop)
<-ctx.Done()
logger.Info("shutting down app gracefully")
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
defer cancel()
if err := closer.Close(shutdownCtx); err != nil {
logger.Errorf("failed to close with graceful shutdown: %v", err)
}
}

@ -1,29 +0,0 @@
package app
import (
"context"
"os"
"os/signal"
"syscall"
"go.mongodb.org/mongo-driver/mongo"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/server"
)
type gracefulShutdownDeps struct {
serverGRPC *server.GRPC
database *mongo.Client
cancel context.CancelFunc
}
func gracefulShutdown(ctx context.Context, deps *gracefulShutdownDeps) {
defer deps.serverGRPC.Stop()
defer deps.database.Disconnect(ctx)
defer deps.cancel()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
<-quit
}

@ -1,31 +1,31 @@
package controller
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/repository"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/service"
)
var GRPCStatuses = map[error]codes.Code{
repository.ErrEmptyArgs: codes.InvalidArgument,
repository.ErrFindRecord: codes.Internal,
repository.ErrInsertRecord: codes.Internal,
repository.ErrInvalidID: codes.InvalidArgument,
repository.ErrMethodNotImplemented: codes.Unimplemented,
repository.ErrNoRecord: codes.NotFound,
repository.ErrTransaction: codes.Internal,
repository.ErrTransactionSessionStart: codes.Internal,
repository.ErrUpdateRecord: codes.Internal,
service.ErrInvalidInputValue: codes.InvalidArgument,
service.ErrInvalidReturnValue: codes.Internal,
}
func determineError(message string, err error) error {
currentStatus, ok := GRPCStatuses[err]
if !ok {
return status.Errorf(codes.Internal, message, err)
}
return status.Errorf(currentStatus, message, err)
}
package controller
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/repository"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/service"
)
var GRPCStatuses = map[error]codes.Code{
repository.ErrEmptyArgs: codes.InvalidArgument,
repository.ErrFindRecord: codes.Internal,
repository.ErrInsertRecord: codes.Internal,
repository.ErrInvalidID: codes.InvalidArgument,
repository.ErrMethodNotImplemented: codes.Unimplemented,
repository.ErrNoRecord: codes.NotFound,
repository.ErrTransaction: codes.Internal,
repository.ErrTransactionSessionStart: codes.Internal,
repository.ErrUpdateRecord: codes.Internal,
service.ErrInvalidInputValue: codes.InvalidArgument,
service.ErrInvalidReturnValue: codes.Internal,
}
func determineError(message string, err error) error {
currentStatus, ok := GRPCStatuses[err]
if !ok {
return status.Errorf(codes.Internal, message, err)
}
return status.Errorf(currentStatus, message, err)
}

@ -1,110 +1,109 @@
package controller
import (
"context"
"github.com/sirupsen/logrus"
"google.golang.org/protobuf/types/known/emptypb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
type DiscountService interface {
ApplyDiscounts(ctx context.Context, request *discount.ApplyDiscountRequest) (*discount.ApplyDiscountResponse, error)
GetDiscountByID(ctx context.Context, id string) (*models.Discount, error)
GetDiscountsByUserID(ctx context.Context, id string) ([]models.Discount, error)
GetAllDiscounts(ctx context.Context) ([]models.Discount, error)
DeleteDiscount(ctx context.Context, id string) (*models.Discount, error)
CreateDiscount(ctx context.Context, discount *models.Discount) (*models.Discount, error)
}
type DiscountControllerDeps struct {
Logger *logrus.Logger
DiscountService DiscountService
}
/*
TODO:
1) Добавить трассировку
2) Добавить валидацию
*/
type DiscountController struct {
logger *logrus.Logger
discountService DiscountService
discount.UnimplementedDiscountServiceServer
}
func NewDiscountController(deps *DiscountControllerDeps) *DiscountController {
return &DiscountController{
discountService: deps.DiscountService,
logger: deps.Logger,
}
}
func (receiver *DiscountController) ApplyDiscounts(ctx context.Context, request *discount.ApplyDiscountRequest) (*discount.ApplyDiscountResponse, error) {
response, err := receiver.discountService.ApplyDiscounts(ctx, request)
if err != nil {
return nil, determineError("failed to apply discounts: %v", err)
}
return response, nil
}
func (receiver *DiscountController) GetDiscountByID(ctx context.Context, request *discount.GetDiscountByIDRequest) (*discount.Discount, error) {
findedDiscount, err := receiver.discountService.GetDiscountByID(ctx, request.ID)
if err != nil {
return nil, determineError("failed to get discount by id: %v", err)
}
return transfer.DiscountModelToProto(*findedDiscount), nil
}
func (receiver *DiscountController) CreateDiscount(ctx context.Context, request *discount.CreateDiscountRequest) (*discount.Discount, error) {
target := transfer.DiscountCalculationTargetProtoToModel(request.Target)
condition := transfer.DiscountConditionProtoToModel(request.Condition)
findedDiscount, err := receiver.discountService.CreateDiscount(ctx, &models.Discount{
Target: *target,
Condition: *condition,
Name: request.Name,
Description: request.Description,
Layer: request.Layer,
})
if err != nil {
return nil, determineError("failed to create discount: %v", err)
}
return transfer.DiscountModelToProto(*findedDiscount), nil
}
func (receiver *DiscountController) DeleteDiscount(ctx context.Context, request *discount.GetDiscountByIDRequest) (*discount.Discount, error) {
deletedDiscount, err := receiver.discountService.DeleteDiscount(ctx, request.ID)
if err != nil {
return nil, determineError("failed to delete discount: %v", err)
}
return transfer.DiscountModelToProto(*deletedDiscount), nil
}
func (receiver *DiscountController) GetUserDiscounts(ctx context.Context, request *discount.GetDiscountByIDRequest) (*discount.Discounts, error) {
userDiscounts, err := receiver.discountService.GetDiscountsByUserID(ctx, request.ID)
if err != nil {
return nil, determineError("failed to get user discounts: %v", err)
}
return transfer.DiscountsModelToProto(userDiscounts), nil
}
func (receiver *DiscountController) GetAllDiscounts(ctx context.Context, _ *emptypb.Empty) (*discount.Discounts, error) {
discounts, err := receiver.discountService.GetAllDiscounts(ctx)
if err != nil {
return nil, determineError("failed to delete discount: %v", err)
}
return transfer.DiscountsModelToProto(discounts), nil
}
package controller
import (
"context"
"github.com/sirupsen/logrus"
"google.golang.org/protobuf/types/known/emptypb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
type DiscountService interface {
ApplyDiscounts(ctx context.Context, request *discount.ApplyDiscountRequest) (*discount.ApplyDiscountResponse, error)
GetDiscountByID(ctx context.Context, id string) (*models.Discount, error)
GetDiscountsByUserID(ctx context.Context, id string) ([]models.Discount, error)
GetAllDiscounts(ctx context.Context) ([]models.Discount, error)
DeleteDiscount(ctx context.Context, id string) (*models.Discount, error)
CreateDiscount(ctx context.Context, discount *models.Discount) (*models.Discount, error)
}
type DiscountControllerDeps struct {
Logger *logrus.Logger
DiscountService DiscountService
}
/*
TODO:
1) Добавить трассировку
2) Добавить валидацию
*/
type DiscountController struct {
logger *logrus.Logger
discountService DiscountService
discount.UnimplementedDiscountServiceServer
}
func NewDiscountController(deps *DiscountControllerDeps) *DiscountController {
return &DiscountController{
discountService: deps.DiscountService,
logger: deps.Logger,
}
}
func (receiver *DiscountController) ApplyDiscounts(ctx context.Context, request *discount.ApplyDiscountRequest) (*discount.ApplyDiscountResponse, error) {
response, err := receiver.discountService.ApplyDiscounts(ctx, request)
if err != nil {
return nil, determineError("failed to apply discounts: %v", err)
}
return response, nil
}
func (receiver *DiscountController) GetDiscountByID(ctx context.Context, request *discount.GetDiscountByIDRequest) (*discount.Discount, error) {
findedDiscount, err := receiver.discountService.GetDiscountByID(ctx, request.ID)
if err != nil {
return nil, determineError("failed to get discount by id: %v", err)
}
return transfer.DiscountModelToProto(*findedDiscount), nil
}
func (receiver *DiscountController) CreateDiscount(ctx context.Context, request *discount.CreateDiscountRequest) (*discount.Discount, error) {
target := transfer.DiscountCalculationTargetProtoToModel(request.Target)
condition := transfer.DiscountConditionProtoToModel(request.Condition)
findedDiscount, err := receiver.discountService.CreateDiscount(ctx, &models.Discount{
Target: *target,
Condition: *condition,
Name: request.Name,
Description: request.Description,
Layer: request.Layer,
})
if err != nil {
return nil, determineError("failed to create discount: %v", err)
}
return transfer.DiscountModelToProto(*findedDiscount), nil
}
func (receiver *DiscountController) DeleteDiscount(ctx context.Context, request *discount.GetDiscountByIDRequest) (*discount.Discount, error) {
deletedDiscount, err := receiver.discountService.DeleteDiscount(ctx, request.ID)
if err != nil {
return nil, determineError("failed to delete discount: %v", err)
}
return transfer.DiscountModelToProto(*deletedDiscount), nil
}
func (receiver *DiscountController) GetUserDiscounts(ctx context.Context, request *discount.GetDiscountByIDRequest) (*discount.Discounts, error) {
userDiscounts, err := receiver.discountService.GetDiscountsByUserID(ctx, request.ID)
if err != nil {
return nil, determineError("failed to get user discounts: %v", err)
}
return transfer.DiscountsModelToProto(userDiscounts), nil
}
func (receiver *DiscountController) GetAllDiscounts(ctx context.Context, _ *emptypb.Empty) (*discount.Discounts, error) {
discounts, err := receiver.discountService.GetAllDiscounts(ctx)
if err != nil {
return nil, determineError("failed to delete discount: %v", err)
}
return transfer.DiscountsModelToProto(discounts), nil
}

@ -1,22 +1,22 @@
package core
import "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/mongo"
type Config struct {
GRPC GRPCConfiguration
HTTP HTTPConfiguration
Database DatabaseConfiguration
}
type GRPCConfiguration struct {
Host string `env:"GRPC_HOST,default=localhost"`
Port string `env:"GPRC_PORT,default=9001"`
}
type HTTPConfiguration struct {
Port string `env:"HTTP_PORT,default=8001"`
}
type DatabaseConfiguration struct {
Connection mongo.Configuration
}
package core
import "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/mongo"
type Config struct {
GRPC GRPCConfiguration
HTTP HTTPConfiguration
Database DatabaseConfiguration
}
type GRPCConfiguration struct {
Host string `env:"GRPC_HOST,default=localhost"`
Port string `env:"GPRC_PORT,default=9001"`
}
type HTTPConfiguration struct {
Port string `env:"HTTP_PORT,default=8001"`
}
type DatabaseConfiguration struct {
Connection mongo.Configuration
}

@ -1,31 +1,31 @@
package core
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
)
type DiscountConditions struct {
Common CommonDiscountCondition `json:"common"`
Optionals []OptionalDiscounCondition `json:"optionals"`
}
type CommonDiscountCondition struct {
Period models.PeriodCondition `json:"period"`
User string `json:"user"`
UserType string `json:"userType"`
Coupon *string `json:"coupon"`
PurchasesAmount float64 `json:"purchasesAmount"`
CartPurchasesAmount float64 `json:"cartPurchasesAmount"`
}
type OptionalDiscounCondition struct {
Product *string `json:"product"`
Group *string `json:"group"`
PriceFrom *float64 `json:"priceFrom"`
/* Срок использования (количество дней использования) */
Term *uint64 `json:"term"`
/* Количество использований (количество попыток) */
Usage *uint64 `json:"usage"`
}
package core
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
)
type DiscountConditions struct {
Common CommonDiscountCondition `json:"common"`
Optionals []OptionalDiscounCondition `json:"optionals"`
}
type CommonDiscountCondition struct {
Period models.PeriodCondition `json:"period"`
User string `json:"user"`
UserType string `json:"userType"`
Coupon *string `json:"coupon"`
PurchasesAmount uint64 `json:"purchasesAmount"`
CartPurchasesAmount uint64 `json:"cartPurchasesAmount"`
}
type OptionalDiscounCondition struct {
Product *string `json:"product"`
Group *string `json:"group"`
PriceFrom *uint64 `json:"priceFrom"`
/* Срок использования (количество дней использования) */
Term *uint64 `json:"term"`
/* Количество использований (количество попыток) */
Usage *uint64 `json:"usage"`
}

@ -1,7 +1,7 @@
package core
type Product struct {
ID string `json:"id"`
Price float64 `json:"price"`
Group string `json:"group"`
}
package core
type Product struct {
ID string `json:"id"`
Price float64 `json:"price"`
Group string `json:"group"`
}

@ -1,31 +1,31 @@
package core
import "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
type UpdateDiscountSettings struct {
ID string
Condition *models.DiscountCondition
Name *string
Description *string
Layer *uint32
Deprecated *bool
Products *[]models.ProductTarget
Factor *float64
TargetScope *models.TargetScope
TargetGroup *string
Overhelm *bool
}
type FilterDiscountSettings struct {
ID *string
Condition *models.DiscountCondition
Name *string
Description *string
Layer *uint32
Deprecated *bool
Products *[]models.ProductTarget
Factor *float64
TargetScope *models.TargetScope
TargetGroup *string
Overhelm *bool
}
package core
import "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
type UpdateDiscountSettings struct {
ID string
Condition *models.DiscountCondition
Name *string
Description *string
Layer *uint32
Deprecated *bool
Products *[]models.ProductTarget
Factor *float64
TargetScope *models.TargetScope
TargetGroup *string
Overhelm *bool
}
type FilterDiscountSettings struct {
ID *string
Condition *models.DiscountCondition
Name *string
Description *string
Layer *uint32
Deprecated *bool
Products *[]models.ProductTarget
Factor *float64
TargetScope *models.TargetScope
TargetGroup *string
Overhelm *bool
}

@ -1,15 +1,15 @@
package fields
import "fmt"
var Audit = struct {
UpdatedAt string
DeletedAt string
CreatedAt string
Deleted string
}{
UpdatedAt: fmt.Sprintf("%s.updatedAt", Discount.Audit),
DeletedAt: fmt.Sprintf("%s.deletedAt", Discount.Audit),
CreatedAt: fmt.Sprintf("%s.createdAt", Discount.Audit),
Deleted: fmt.Sprintf("%s.deleted", Discount.Audit),
}
package fields
import "fmt"
var Audit = struct {
UpdatedAt string
DeletedAt string
CreatedAt string
Deleted string
}{
UpdatedAt: fmt.Sprintf("%s.updatedAt", Discount.Audit),
DeletedAt: fmt.Sprintf("%s.deletedAt", Discount.Audit),
CreatedAt: fmt.Sprintf("%s.createdAt", Discount.Audit),
Deleted: fmt.Sprintf("%s.deleted", Discount.Audit),
}

@ -1,71 +1,71 @@
package fields
import "fmt"
var Discount = struct {
ID string
Name string
Description string
Target string
Condition string
Layer string
Deprecated string
Audit string
}{
ID: "_id",
Name: "name",
Description: "description",
Target: "target",
Condition: "condition",
Layer: "layer",
Deprecated: "deprecated",
Audit: "audit",
}
var DiscountTarget = struct {
Products string
Factor string
TargetScope string
TargetGroup string
Overhelm string
}{
Products: fmt.Sprintf("%s.products", Discount.Target),
Factor: fmt.Sprintf("%s.factor", Discount.Target),
TargetScope: fmt.Sprintf("%s.scope", Discount.Target),
TargetGroup: fmt.Sprintf("%s.group", Discount.Target),
Overhelm: fmt.Sprintf("%s.overhelm", Discount.Target),
}
var DiscountCondition = struct {
Period string
Product string
Term string
Usage string
PriceFrom string
Group string
User string
UserType string
Coupon string
PurchasesAmount string
CartPurchasesAmount string
}{
Period: fmt.Sprintf("%s.period", Discount.Condition),
Product: fmt.Sprintf("%s.product", Discount.Condition),
Term: fmt.Sprintf("%s.term", Discount.Condition),
Usage: fmt.Sprintf("%s.usage", Discount.Condition),
PriceFrom: fmt.Sprintf("%s.priceFrom", Discount.Condition),
Group: fmt.Sprintf("%s.group", Discount.Condition),
User: fmt.Sprintf("%s.user", Discount.Condition),
UserType: fmt.Sprintf("%s.userType", Discount.Condition),
Coupon: fmt.Sprintf("%s.coupon", Discount.Condition),
PurchasesAmount: fmt.Sprintf("%s.purchasesAmount", Discount.Condition),
CartPurchasesAmount: fmt.Sprintf("%s.cartPurchasesAmount", Discount.Condition),
}
var PeriodCondition = struct {
From string
To string
}{
From: fmt.Sprintf("%s.from", DiscountCondition.Period),
To: fmt.Sprintf("%s.to", DiscountCondition.Period),
}
package fields
import "fmt"
var Discount = struct {
ID string
Name string
Description string
Target string
Condition string
Layer string
Deprecated string
Audit string
}{
ID: "_id",
Name: "name",
Description: "description",
Target: "target",
Condition: "condition",
Layer: "layer",
Deprecated: "deprecated",
Audit: "audit",
}
var DiscountTarget = struct {
Products string
Factor string
TargetScope string
TargetGroup string
Overhelm string
}{
Products: fmt.Sprintf("%s.products", Discount.Target),
Factor: fmt.Sprintf("%s.factor", Discount.Target),
TargetScope: fmt.Sprintf("%s.scope", Discount.Target),
TargetGroup: fmt.Sprintf("%s.group", Discount.Target),
Overhelm: fmt.Sprintf("%s.overhelm", Discount.Target),
}
var DiscountCondition = struct {
Period string
Product string
Term string
Usage string
PriceFrom string
Group string
User string
UserType string
Coupon string
PurchasesAmount string
CartPurchasesAmount string
}{
Period: fmt.Sprintf("%s.period", Discount.Condition),
Product: fmt.Sprintf("%s.product", Discount.Condition),
Term: fmt.Sprintf("%s.term", Discount.Condition),
Usage: fmt.Sprintf("%s.usage", Discount.Condition),
PriceFrom: fmt.Sprintf("%s.priceFrom", Discount.Condition),
Group: fmt.Sprintf("%s.group", Discount.Condition),
User: fmt.Sprintf("%s.user", Discount.Condition),
UserType: fmt.Sprintf("%s.userType", Discount.Condition),
Coupon: fmt.Sprintf("%s.coupon", Discount.Condition),
PurchasesAmount: fmt.Sprintf("%s.purchasesAmount", Discount.Condition),
CartPurchasesAmount: fmt.Sprintf("%s.cartPurchasesAmount", Discount.Condition),
}
var PeriodCondition = struct {
From string
To string
}{
From: fmt.Sprintf("%s.from", DiscountCondition.Period),
To: fmt.Sprintf("%s.to", DiscountCondition.Period),
}

@ -1,24 +1,24 @@
package initialize
import (
"github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/controller"
)
type ControllersDeps struct {
Logger *logrus.Logger
Services *Services
}
type Controllers struct {
DiscountController *controller.DiscountController
}
func NewControllers(deps *ControllersDeps) *Controllers {
return &Controllers{
DiscountController: controller.NewDiscountController(&controller.DiscountControllerDeps{
Logger: deps.Logger,
DiscountService: deps.Services.DiscountService,
}),
}
}
package initialize
import (
"github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/controller"
)
type ControllersDeps struct {
Logger *logrus.Logger
Services *Services
}
type Controllers struct {
DiscountController *controller.DiscountController
}
func NewControllers(deps *ControllersDeps) *Controllers {
return &Controllers{
DiscountController: controller.NewDiscountController(&controller.DiscountControllerDeps{
Logger: deps.Logger,
DiscountService: deps.Services.DiscountService,
}),
}
}

@ -1,23 +1,22 @@
package initialize
import (
"github.com/sirupsen/logrus"
"go.mongodb.org/mongo-driver/mongo"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/repository"
)
type RepositoriesDeps struct {
Database *mongo.Database
Logger *logrus.Logger
}
type Repositories struct {
DiscountRepository *repository.DiscountRepository
}
func NewRepositories(deps *RepositoriesDeps) *Repositories {
return &Repositories{
DiscountRepository: repository.NewDiscountRepository(deps.Database.Collection("discounts"), deps.Logger),
}
}
package initialize
import (
"github.com/sirupsen/logrus"
"go.mongodb.org/mongo-driver/mongo"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/repository"
)
type RepositoriesDeps struct {
Database *mongo.Database
Logger *logrus.Logger
}
type Repositories struct {
DiscountRepository *repository.DiscountRepository
}
func NewRepositories(deps *RepositoriesDeps) *Repositories {
return &Repositories{
DiscountRepository: repository.NewDiscountRepository(deps.Database.Collection("discounts"), deps.Logger),
}
}

@ -1,24 +1,24 @@
package initialize
import (
"github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/service"
)
type ServicesDeps struct {
Logger *logrus.Logger
Respositories *Repositories
}
type Services struct {
DiscountService *service.DiscountService
}
func NewServices(deps *ServicesDeps) *Services {
return &Services{
DiscountService: service.NewDiscountService(&service.DiscountServiceDeps{
Logger: deps.Logger,
DiscountRepository: deps.Respositories.DiscountRepository,
}),
}
}
package initialize
import (
"github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/service"
)
type ServicesDeps struct {
Logger *logrus.Logger
Repositories *Repositories
}
type Services struct {
DiscountService *service.DiscountService
}
func NewServices(deps *ServicesDeps) *Services {
return &Services{
DiscountService: service.NewDiscountService(&service.DiscountServiceDeps{
Logger: deps.Logger,
DiscountRepository: deps.Repositories.DiscountRepository,
}),
}
}

@ -36,19 +36,19 @@ type DiscountCalculationTarget struct {
type DiscountCondition struct {
Period *PeriodCondition `json:"period,omitempty" bson:"period,omitempty"`
Product *string `json:"product,omitempty" bson:"product,omitempty"`
PriceFrom *float64 `json:"priceFrom,omitempty" bson:"priceFrom,omitempty"`
PriceFrom *uint64 `json:"priceFrom,omitempty" bson:"priceFrom,omitempty"`
Group *string `json:"group,omitempty" bson:"group,omitempty"`
User *string `json:"user,omitempty" bson:"user,omitempty"`
UserType *string `json:"userType,omitempty" bson:"userType,omitempty"`
Coupon *string `json:"coupon,omitempty" bson:"coupon,omitempty"`
PurchasesAmount *float64 `json:"purchasesAmount,omitempty" bson:"purchasesAmount,omitempty"`
CartPurchasesAmount *float64 `json:"cartPurchasesAmount,omitempty" bson:"cartPurchasesAmount,omitempty"`
PurchasesAmount *uint64 `json:"purchasesAmount,omitempty" bson:"purchasesAmount,omitempty"`
CartPurchasesAmount *uint64 `json:"cartPurchasesAmount,omitempty" bson:"cartPurchasesAmount,omitempty"`
/* Срок использования (количество дней использования) */
Term *uint64 `json:"term,omitempty" bson:"term,omitempty"`
Term *uint64 `json:"term" bson:"term"`
/* Количество использований (количество попыток) */
Usage *uint64 `json:"usage,omitempty" bson:"usage,omitempty"`
Usage *uint64 `json:"usage" bson:"usage,omitempty"`
}
type ProductTarget struct {
@ -65,9 +65,14 @@ type PeriodCondition struct {
type TargetScope string
const (
TargetSum TargetScope = "sum"
// TargetSum тип скидки, применяющийся на финальную сформированную сумму всех товаров.
TargetSum TargetScope = "sum"
// TargetGroup тип скидки, применяющийся на сумму товаров определённой группы.
TargetGroup TargetScope = "group"
TargetEach TargetScope = "each"
// TargetEach тип скидки, применяющийся на определённый товар.
TargetEach TargetScope = "each"
)
var TargetScopeModelMap = map[discount.TargetScope]TargetScope{

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: discount/audit.model.proto

@ -34,6 +34,7 @@
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: discount/discount.model.proto
@ -397,12 +397,12 @@ type DiscountCondition struct {
User *string `protobuf:"bytes,2,opt,name=User,proto3,oneof" json:"User,omitempty"`
UserType *string `protobuf:"bytes,3,opt,name=UserType,proto3,oneof" json:"UserType,omitempty"`
Coupon *string `protobuf:"bytes,4,opt,name=Coupon,proto3,oneof" json:"Coupon,omitempty"`
PurchasesAmount *float64 `protobuf:"fixed64,5,opt,name=PurchasesAmount,proto3,oneof" json:"PurchasesAmount,omitempty"`
CartPurchasesAmount *float64 `protobuf:"fixed64,6,opt,name=CartPurchasesAmount,proto3,oneof" json:"CartPurchasesAmount,omitempty"`
PurchasesAmount *uint64 `protobuf:"varint,5,opt,name=PurchasesAmount,proto3,oneof" json:"PurchasesAmount,omitempty"`
CartPurchasesAmount *uint64 `protobuf:"varint,6,opt,name=CartPurchasesAmount,proto3,oneof" json:"CartPurchasesAmount,omitempty"`
Product *string `protobuf:"bytes,7,opt,name=Product,proto3,oneof" json:"Product,omitempty"`
Term *uint64 `protobuf:"varint,8,opt,name=Term,proto3,oneof" json:"Term,omitempty"`
Usage *uint64 `protobuf:"varint,9,opt,name=Usage,proto3,oneof" json:"Usage,omitempty"`
PriceFrom *float64 `protobuf:"fixed64,10,opt,name=PriceFrom,proto3,oneof" json:"PriceFrom,omitempty"`
PriceFrom *uint64 `protobuf:"varint,10,opt,name=PriceFrom,proto3,oneof" json:"PriceFrom,omitempty"`
Group *string `protobuf:"bytes,11,opt,name=Group,proto3,oneof" json:"Group,omitempty"`
}
@ -466,14 +466,14 @@ func (x *DiscountCondition) GetCoupon() string {
return ""
}
func (x *DiscountCondition) GetPurchasesAmount() float64 {
func (x *DiscountCondition) GetPurchasesAmount() uint64 {
if x != nil && x.PurchasesAmount != nil {
return *x.PurchasesAmount
}
return 0
}
func (x *DiscountCondition) GetCartPurchasesAmount() float64 {
func (x *DiscountCondition) GetCartPurchasesAmount() uint64 {
if x != nil && x.CartPurchasesAmount != nil {
return *x.CartPurchasesAmount
}
@ -501,7 +501,7 @@ func (x *DiscountCondition) GetUsage() uint64 {
return 0
}
func (x *DiscountCondition) GetPriceFrom() float64 {
func (x *DiscountCondition) GetPriceFrom() uint64 {
if x != nil && x.PriceFrom != nil {
return *x.PriceFrom
}
@ -638,10 +638,10 @@ type UserInformation struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
Type string `protobuf:"bytes,2,opt,name=Type,proto3" json:"Type,omitempty"`
PurchasesAmount float64 `protobuf:"fixed64,3,opt,name=PurchasesAmount,proto3" json:"PurchasesAmount,omitempty"`
CartPurchasesAmount float64 `protobuf:"fixed64,4,opt,name=CartPurchasesAmount,proto3" json:"CartPurchasesAmount,omitempty"`
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
Type string `protobuf:"bytes,2,opt,name=Type,proto3" json:"Type,omitempty"`
PurchasesAmount uint64 `protobuf:"varint,3,opt,name=PurchasesAmount,proto3" json:"PurchasesAmount,omitempty"`
CartPurchasesAmount uint64 `protobuf:"varint,4,opt,name=CartPurchasesAmount,proto3" json:"CartPurchasesAmount,omitempty"`
}
func (x *UserInformation) Reset() {
@ -690,14 +690,14 @@ func (x *UserInformation) GetType() string {
return ""
}
func (x *UserInformation) GetPurchasesAmount() float64 {
func (x *UserInformation) GetPurchasesAmount() uint64 {
if x != nil {
return x.PurchasesAmount
}
return 0
}
func (x *UserInformation) GetCartPurchasesAmount() float64 {
func (x *UserInformation) GetCartPurchasesAmount() uint64 {
if x != nil {
return x.CartPurchasesAmount
}
@ -710,7 +710,7 @@ type ProductInformation struct {
unknownFields protoimpl.UnknownFields
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
Price float64 `protobuf:"fixed64,2,opt,name=Price,proto3" json:"Price,omitempty"`
Price uint64 `protobuf:"varint,2,opt,name=Price,proto3" json:"Price,omitempty"`
Term *uint64 `protobuf:"varint,3,opt,name=Term,proto3,oneof" json:"Term,omitempty"`
Usage *uint64 `protobuf:"varint,4,opt,name=Usage,proto3,oneof" json:"Usage,omitempty"`
Group *string `protobuf:"bytes,5,opt,name=Group,proto3,oneof" json:"Group,omitempty"`
@ -755,7 +755,7 @@ func (x *ProductInformation) GetID() string {
return ""
}
func (x *ProductInformation) GetPrice() float64 {
func (x *ProductInformation) GetPrice() uint64 {
if x != nil {
return x.Price
}
@ -868,10 +868,10 @@ var file_discount_discount_model_proto_rawDesc = []byte{
0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x06,
0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x0f, 0x50, 0x75, 0x72,
0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01,
0x28, 0x01, 0x48, 0x04, 0x52, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41,
0x28, 0x04, 0x48, 0x04, 0x52, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x13, 0x43, 0x61, 0x72, 0x74,
0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18,
0x06, 0x20, 0x01, 0x28, 0x01, 0x48, 0x05, 0x52, 0x13, 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, 0x72,
0x06, 0x20, 0x01, 0x28, 0x04, 0x48, 0x05, 0x52, 0x13, 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, 0x72,
0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12,
0x1d, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
0x48, 0x06, 0x52, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x88, 0x01, 0x01, 0x12, 0x17,
@ -879,7 +879,7 @@ var file_discount_discount_model_proto_rawDesc = []byte{
0x54, 0x65, 0x72, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65,
0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x48, 0x08, 0x52, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x88,
0x01, 0x01, 0x12, 0x21, 0x0a, 0x09, 0x50, 0x72, 0x69, 0x63, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x01, 0x48, 0x09, 0x52, 0x09, 0x50, 0x72, 0x69, 0x63, 0x65, 0x46, 0x72,
0x0a, 0x20, 0x01, 0x28, 0x04, 0x48, 0x09, 0x52, 0x09, 0x50, 0x72, 0x69, 0x63, 0x65, 0x46, 0x72,
0x6f, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x0b,
0x20, 0x01, 0x28, 0x09, 0x48, 0x0a, 0x52, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x88, 0x01, 0x01,
0x42, 0x09, 0x0a, 0x07, 0x5f, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x5f,
@ -909,14 +909,14 @@ var file_discount_discount_model_proto_rawDesc = []byte{
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a,
0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65,
0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65,
0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x13, 0x43, 0x61, 0x72, 0x74, 0x50,
0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04,
0x20, 0x01, 0x28, 0x01, 0x52, 0x13, 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61,
0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61,
0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xa6, 0x01, 0x0a, 0x12, 0x50, 0x72,
0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44,
0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52,
0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x54, 0x65, 0x72, 0x6d, 0x18, 0x03,
0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x04, 0x54, 0x65, 0x72, 0x6d, 0x88, 0x01, 0x01, 0x12,
0x19, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01,

@ -34,6 +34,7 @@
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: discount/service.proto
@ -146,7 +146,7 @@ type ApplyDiscountResponse struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Price float64 `protobuf:"fixed64,1,opt,name=Price,proto3" json:"Price,omitempty"`
Price uint64 `protobuf:"varint,1,opt,name=Price,proto3" json:"Price,omitempty"`
AppliedDiscounts []*Discount `protobuf:"bytes,2,rep,name=AppliedDiscounts,proto3" json:"AppliedDiscounts,omitempty"`
}
@ -182,7 +182,7 @@ func (*ApplyDiscountResponse) Descriptor() ([]byte, []int) {
return file_discount_service_proto_rawDescGZIP(), []int{2}
}
func (x *ApplyDiscountResponse) GetPrice() float64 {
func (x *ApplyDiscountResponse) GetPrice() uint64 {
if x != nil {
return x.Price
}
@ -307,7 +307,7 @@ var file_discount_service_proto_rawDesc = []byte{
0x00, 0x52, 0x06, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07,
0x5f, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x22, 0x6d, 0x0a, 0x15, 0x41, 0x70, 0x70, 0x6c, 0x79,
0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52,
0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65,
0x64, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63,

@ -681,7 +681,7 @@ func RegisterDiscountServiceHandlerServer(ctx context.Context, mux *runtime.Serv
// RegisterDiscountServiceHandlerFromEndpoint is same as RegisterDiscountServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterDiscountServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.Dial(endpoint, opts...)
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}

@ -341,6 +341,7 @@
"Products": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/discountProductInformation"
}
},
@ -363,6 +364,7 @@
"AppliedDiscounts": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/discountDiscount"
}
}
@ -445,6 +447,7 @@
"Products": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/discountProductTarget"
}
},
@ -512,6 +515,7 @@
"Discounts": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/discountDiscount"
}
}
@ -618,6 +622,7 @@
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}

@ -1,4 +1,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: discount/service.proto
package discount
@ -15,6 +19,18 @@ import (
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
DiscountService_GetAllDiscounts_FullMethodName = "/discount.DiscountService/GetAllDiscounts"
DiscountService_GetUserDiscounts_FullMethodName = "/discount.DiscountService/GetUserDiscounts"
DiscountService_DetermineDiscounts_FullMethodName = "/discount.DiscountService/DetermineDiscounts"
DiscountService_ApplyDiscounts_FullMethodName = "/discount.DiscountService/ApplyDiscounts"
DiscountService_GetDiscountByID_FullMethodName = "/discount.DiscountService/GetDiscountByID"
DiscountService_CreateDiscount_FullMethodName = "/discount.DiscountService/CreateDiscount"
DiscountService_ReplaceDiscount_FullMethodName = "/discount.DiscountService/ReplaceDiscount"
DiscountService_UpdateDiscount_FullMethodName = "/discount.DiscountService/UpdateDiscount"
DiscountService_DeleteDiscount_FullMethodName = "/discount.DiscountService/DeleteDiscount"
)
// DiscountServiceClient is the client API for DiscountService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
@ -40,7 +56,7 @@ func NewDiscountServiceClient(cc grpc.ClientConnInterface) DiscountServiceClient
func (c *discountServiceClient) GetAllDiscounts(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Discounts, error) {
out := new(Discounts)
err := c.cc.Invoke(ctx, "/discount.DiscountService/GetAllDiscounts", in, out, opts...)
err := c.cc.Invoke(ctx, DiscountService_GetAllDiscounts_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -49,7 +65,7 @@ func (c *discountServiceClient) GetAllDiscounts(ctx context.Context, in *emptypb
func (c *discountServiceClient) GetUserDiscounts(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discounts, error) {
out := new(Discounts)
err := c.cc.Invoke(ctx, "/discount.DiscountService/GetUserDiscounts", in, out, opts...)
err := c.cc.Invoke(ctx, DiscountService_GetUserDiscounts_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -58,7 +74,7 @@ func (c *discountServiceClient) GetUserDiscounts(ctx context.Context, in *GetDis
func (c *discountServiceClient) DetermineDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*Discounts, error) {
out := new(Discounts)
err := c.cc.Invoke(ctx, "/discount.DiscountService/DetermineDiscounts", in, out, opts...)
err := c.cc.Invoke(ctx, DiscountService_DetermineDiscounts_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -67,7 +83,7 @@ func (c *discountServiceClient) DetermineDiscounts(ctx context.Context, in *Appl
func (c *discountServiceClient) ApplyDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*ApplyDiscountResponse, error) {
out := new(ApplyDiscountResponse)
err := c.cc.Invoke(ctx, "/discount.DiscountService/ApplyDiscounts", in, out, opts...)
err := c.cc.Invoke(ctx, DiscountService_ApplyDiscounts_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -76,7 +92,7 @@ func (c *discountServiceClient) ApplyDiscounts(ctx context.Context, in *ApplyDis
func (c *discountServiceClient) GetDiscountByID(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error) {
out := new(Discount)
err := c.cc.Invoke(ctx, "/discount.DiscountService/GetDiscountByID", in, out, opts...)
err := c.cc.Invoke(ctx, DiscountService_GetDiscountByID_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -85,7 +101,7 @@ func (c *discountServiceClient) GetDiscountByID(ctx context.Context, in *GetDisc
func (c *discountServiceClient) CreateDiscount(ctx context.Context, in *CreateDiscountRequest, opts ...grpc.CallOption) (*Discount, error) {
out := new(Discount)
err := c.cc.Invoke(ctx, "/discount.DiscountService/CreateDiscount", in, out, opts...)
err := c.cc.Invoke(ctx, DiscountService_CreateDiscount_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -94,7 +110,7 @@ func (c *discountServiceClient) CreateDiscount(ctx context.Context, in *CreateDi
func (c *discountServiceClient) ReplaceDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error) {
out := new(Discount)
err := c.cc.Invoke(ctx, "/discount.DiscountService/ReplaceDiscount", in, out, opts...)
err := c.cc.Invoke(ctx, DiscountService_ReplaceDiscount_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -103,7 +119,7 @@ func (c *discountServiceClient) ReplaceDiscount(ctx context.Context, in *Discoun
func (c *discountServiceClient) UpdateDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error) {
out := new(Discount)
err := c.cc.Invoke(ctx, "/discount.DiscountService/UpdateDiscount", in, out, opts...)
err := c.cc.Invoke(ctx, DiscountService_UpdateDiscount_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -112,7 +128,7 @@ func (c *discountServiceClient) UpdateDiscount(ctx context.Context, in *Discount
func (c *discountServiceClient) DeleteDiscount(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error) {
out := new(Discount)
err := c.cc.Invoke(ctx, "/discount.DiscountService/DeleteDiscount", in, out, opts...)
err := c.cc.Invoke(ctx, DiscountService_DeleteDiscount_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -187,7 +203,7 @@ func _DiscountService_GetAllDiscounts_Handler(srv interface{}, ctx context.Conte
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/discount.DiscountService/GetAllDiscounts",
FullMethod: DiscountService_GetAllDiscounts_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).GetAllDiscounts(ctx, req.(*emptypb.Empty))
@ -205,7 +221,7 @@ func _DiscountService_GetUserDiscounts_Handler(srv interface{}, ctx context.Cont
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/discount.DiscountService/GetUserDiscounts",
FullMethod: DiscountService_GetUserDiscounts_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).GetUserDiscounts(ctx, req.(*GetDiscountByIDRequest))
@ -223,7 +239,7 @@ func _DiscountService_DetermineDiscounts_Handler(srv interface{}, ctx context.Co
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/discount.DiscountService/DetermineDiscounts",
FullMethod: DiscountService_DetermineDiscounts_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).DetermineDiscounts(ctx, req.(*ApplyDiscountRequest))
@ -241,7 +257,7 @@ func _DiscountService_ApplyDiscounts_Handler(srv interface{}, ctx context.Contex
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/discount.DiscountService/ApplyDiscounts",
FullMethod: DiscountService_ApplyDiscounts_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).ApplyDiscounts(ctx, req.(*ApplyDiscountRequest))
@ -259,7 +275,7 @@ func _DiscountService_GetDiscountByID_Handler(srv interface{}, ctx context.Conte
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/discount.DiscountService/GetDiscountByID",
FullMethod: DiscountService_GetDiscountByID_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).GetDiscountByID(ctx, req.(*GetDiscountByIDRequest))
@ -277,7 +293,7 @@ func _DiscountService_CreateDiscount_Handler(srv interface{}, ctx context.Contex
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/discount.DiscountService/CreateDiscount",
FullMethod: DiscountService_CreateDiscount_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).CreateDiscount(ctx, req.(*CreateDiscountRequest))
@ -295,7 +311,7 @@ func _DiscountService_ReplaceDiscount_Handler(srv interface{}, ctx context.Conte
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/discount.DiscountService/ReplaceDiscount",
FullMethod: DiscountService_ReplaceDiscount_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).ReplaceDiscount(ctx, req.(*DiscountOptional))
@ -313,7 +329,7 @@ func _DiscountService_UpdateDiscount_Handler(srv interface{}, ctx context.Contex
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/discount.DiscountService/UpdateDiscount",
FullMethod: DiscountService_UpdateDiscount_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).UpdateDiscount(ctx, req.(*DiscountOptional))
@ -331,7 +347,7 @@ func _DiscountService_DeleteDiscount_Handler(srv interface{}, ctx context.Contex
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/discount.DiscountService/DeleteDiscount",
FullMethod: DiscountService_DeleteDiscount_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).DeleteDiscount(ctx, req.(*GetDiscountByIDRequest))

@ -11,7 +11,6 @@ import (
"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"
@ -184,7 +183,9 @@ func (receiver *DiscountRepository) UpdateMany(ctx context.Context, updates []co
if _, transactionError := session.WithTransaction(ctx, func(sessionContext mongo.SessionContext) (interface{}, error) {
for _, update := range updates {
if err := receiver.UpdateOne(sessionContext, &update); err != nil {
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
}

@ -1,73 +1,72 @@
package server
import (
"fmt"
"net"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
type GRPC struct {
logger *logrus.Entry
grpc *grpc.Server
}
func NewGRPC(logger *logrus.Logger) *GRPC {
grpcLogger := logrus.NewEntry(logger)
grpcStreamInterceptor := grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_logrus.StreamServerInterceptor(grpcLogger),
grpc_recovery.StreamServerInterceptor(),
))
grpcUnaryInterceptor := grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
grpc_logrus.UnaryServerInterceptor(grpcLogger),
grpc_recovery.UnaryServerInterceptor(),
))
return &GRPC{
grpc: grpc.NewServer(grpcStreamInterceptor, grpcUnaryInterceptor),
logger: grpcLogger,
}
}
func (receiver *GRPC) Run(config *core.GRPCConfiguration) {
connectionString := fmt.Sprintf("%s:%s", config.Host, config.Port)
receiver.logger.Infof("Starting GRPC Server on %s", connectionString)
if err := receiver.listen(connectionString); err != nil && err != grpc.ErrServerStopped {
receiver.logger.Errorf("GRPC Listen error: %v", err)
panic(err)
}
}
func (receiver *GRPC) Register(server discount.DiscountServiceServer) *GRPC {
discount.RegisterDiscountServiceServer(receiver.grpc, server)
return receiver
}
func (receiver *GRPC) listen(address string) error {
listener, err := net.Listen("tcp", address)
if err != nil {
return err
}
if err := receiver.grpc.Serve(listener); err != nil {
return err
}
return nil
}
func (receiver *GRPC) Stop() {
receiver.grpc.GracefulStop()
receiver.logger.Infoln("Shutting down GRPC server...")
}
package server
import (
"context"
"fmt"
"net"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
type GRPC struct {
logger *logrus.Entry
grpc *grpc.Server
}
func NewGRPC(logger *logrus.Logger) *GRPC {
grpcLogger := logrus.NewEntry(logger)
grpcStreamInterceptor := grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_logrus.StreamServerInterceptor(grpcLogger),
grpc_recovery.StreamServerInterceptor(),
))
grpcUnaryInterceptor := grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
grpc_logrus.UnaryServerInterceptor(grpcLogger),
grpc_recovery.UnaryServerInterceptor(),
))
return &GRPC{
grpc: grpc.NewServer(grpcStreamInterceptor, grpcUnaryInterceptor),
logger: grpcLogger,
}
}
func (receiver *GRPC) Run(config *core.GRPCConfiguration) {
connectionString := fmt.Sprintf("%s:%s", config.Host, config.Port)
receiver.logger.Infof("Starting GRPC Server on %s", connectionString)
if err := receiver.listen(connectionString); err != nil && err != grpc.ErrServerStopped {
receiver.logger.Errorf("GRPC Listen error: %v", err)
panic(err)
}
}
func (receiver *GRPC) Register(server discount.DiscountServiceServer) *GRPC {
discount.RegisterDiscountServiceServer(receiver.grpc, server)
return receiver
}
func (receiver *GRPC) listen(address string) error {
listener, err := net.Listen("tcp", address)
if err != nil {
return err
}
return receiver.grpc.Serve(listener)
}
func (receiver *GRPC) Stop(_ context.Context) error {
receiver.grpc.GracefulStop()
receiver.logger.Infoln("Shutting down GRPC server...")
return nil
}

@ -1,50 +1,64 @@
package server
import (
"context"
"fmt"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
type HTTP struct {
logger *logrus.Logger
mux *runtime.ServeMux
}
func NewHTTP(logger *logrus.Logger) *HTTP {
return &HTTP{
logger: logger,
mux: runtime.NewServeMux(),
}
}
func (receiver *HTTP) Register(ctx context.Context, config *core.GRPCConfiguration) *HTTP {
options := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
endpoint := fmt.Sprintf("%s:%s", config.Host, config.Port)
if err := discount.RegisterDiscountServiceHandlerFromEndpoint(ctx, receiver.mux, endpoint, options); err != nil {
receiver.logger.Errorf("HTTP register error: %v", err)
panic(err)
}
return receiver
}
func (receiver *HTTP) Run(config *core.HTTPConfiguration) {
endpoint := fmt.Sprintf(":%s", config.Port)
receiver.logger.Infof("HTTP server started on: <%s>", endpoint)
if err := http.ListenAndServe(endpoint, receiver.mux); err != nil && err != http.ErrServerClosed {
receiver.logger.Errorf("HTTP listen error: %v", err)
panic(err)
}
}
package server
import (
"context"
"fmt"
"net/http"
"time"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
type HTTP struct {
logger *logrus.Logger
server *http.Server
mux *runtime.ServeMux
}
func NewHTTP(logger *logrus.Logger) *HTTP {
return &HTTP{
logger: logger,
mux: runtime.NewServeMux(),
server: &http.Server{
MaxHeaderBytes: 1 << 20,
ReadTimeout: 20 * time.Second,
WriteTimeout: 20 * time.Second,
IdleTimeout: 20 * time.Second,
},
}
}
func (receiver *HTTP) Register(ctx context.Context, config *core.GRPCConfiguration) *HTTP {
options := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
endpoint := fmt.Sprintf("%s:%s", config.Host, config.Port)
if err := discount.RegisterDiscountServiceHandlerFromEndpoint(ctx, receiver.mux, endpoint, options); err != nil {
receiver.logger.Errorf("HTTP register error: %v", err)
panic(err)
}
return receiver
}
func (receiver *HTTP) Listen(address string) error {
receiver.server.Addr = address
receiver.server.Handler = receiver.mux
return receiver.server.ListenAndServe()
}
func (receiver *HTTP) Run(config *core.HTTPConfiguration) {
endpoint := fmt.Sprintf(":%s", config.Port)
receiver.logger.Infof("HTTP server started on: <%s>", endpoint)
if err := receiver.Listen(endpoint); err != nil && err != http.ErrServerClosed {
receiver.logger.Errorf("HTTP listen error: %v", err)
panic(err)
}
}

@ -1,136 +1,135 @@
package service
import (
"context"
"github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/calculate"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
type DiscountRepository interface {
Insert(ctx context.Context, discount *models.Discount) (string, error)
DeleteByID(ctx context.Context, discountID string) (*models.Discount, error)
FindByID(ctx context.Context, discountID string) (*models.Discount, error)
FindAll(ctx context.Context) ([]models.Discount, error)
FindByUserID(ctx context.Context, userID string) ([]models.Discount, error)
Determine(ctx context.Context, conditions *core.DiscountConditions) ([]models.Discount, error)
UpdateOne(ctx context.Context, update *core.UpdateDiscountSettings) error
}
type DiscountServiceDeps struct {
Logger *logrus.Logger
DiscountRepository DiscountRepository
}
type DiscountService struct {
logger *logrus.Logger
discountRepository DiscountRepository
}
func NewDiscountService(deps *DiscountServiceDeps) *DiscountService {
return &DiscountService{
logger: deps.Logger,
discountRepository: deps.DiscountRepository,
}
}
func (receiver *DiscountService) ApplyDiscounts(ctx context.Context, request *discount.ApplyDiscountRequest) (*discount.ApplyDiscountResponse, error) {
conditions, err := utils.ConstituteConditions(request)
if err != nil {
receiver.logger.Errorf("failed to constitute conditions on <ApplyDiscounts> of <DiscountService>: %v", err)
return nil, ErrInvalidInputValue
}
discounts, err := receiver.discountRepository.Determine(ctx, conditions)
if err != nil {
receiver.logger.Errorf("failed to determine conditions on <ApplyDiscounts> of <DiscountService>: %v", err)
return nil, err
}
filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts(discounts)
if couponDiscount != nil {
deprecated := true
if err := receiver.discountRepository.UpdateOne(ctx, &core.UpdateDiscountSettings{
ID: couponDiscount.ID,
Deprecated: &deprecated,
}); err != nil {
receiver.logger.Errorf("failed to deprecate coupon discount on <ApplyDiscounts> of <DiscountService>: %v", err)
return nil, err
}
}
products := transfer.ProductsProtoToCore(request.Products)
return &discount.ApplyDiscountResponse{
Price: calculate.Discount(products, filteredDiscounts),
AppliedDiscounts: transfer.DiscountsModelToProto(filteredDiscounts).Discounts,
}, nil
}
func (receiver *DiscountService) GetDiscountByID(ctx context.Context, id string) (*models.Discount, error) {
discount, err := receiver.discountRepository.FindByID(ctx, id)
if err != nil {
receiver.logger.Errorf("failed to get discount on <GetDiscountByID> of <DiscountService>: %v", err)
return nil, err
}
return discount, nil
}
func (receiver *DiscountService) GetDiscountsByUserID(ctx context.Context, id string) ([]models.Discount, error) {
discounts, err := receiver.discountRepository.FindByUserID(ctx, id)
if err != nil {
receiver.logger.Errorf("failed to get discount on <GetDiscountByUserID> of <DiscountService>: %v", err)
return nil, err
}
return discounts, nil
}
func (receiver *DiscountService) GetAllDiscounts(ctx context.Context) ([]models.Discount, error) {
discounts, err := receiver.discountRepository.FindAll(ctx)
if err != nil {
receiver.logger.Errorf("failed to get all discounts on <GetAllDiscounts> of <DiscountService>: %v", err)
return nil, err
}
return discounts, nil
}
func (receiver *DiscountService) DeleteDiscount(ctx context.Context, id string) (*models.Discount, error) {
deletedDiscount, err := receiver.discountRepository.DeleteByID(ctx, id)
if err != nil {
receiver.logger.Errorf("failed delete discount on <DeleteDiscount> of <DiscountService>: %v", err)
return nil, err
}
return deletedDiscount, nil
}
func (receiver *DiscountService) CreateDiscount(ctx context.Context, discount *models.Discount) (*models.Discount, error) {
createdDiscountID, err := receiver.discountRepository.Insert(ctx, discount)
if err != nil {
return nil, err
}
if createdDiscountID == "" {
receiver.logger.Errorf("empty discount ID on <CreateDiscount> of <DiscountService>")
return nil, ErrInvalidReturnValue
}
return &models.Discount{
Target: discount.Target,
Condition: discount.Condition,
ID: createdDiscountID,
Name: discount.Name,
Description: discount.Description,
Layer: discount.Layer,
}, nil
}
package service
import (
"context"
"github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils"
discountUtils "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
type DiscountRepository interface {
Insert(ctx context.Context, discount *models.Discount) (string, error)
DeleteByID(ctx context.Context, discountID string) (*models.Discount, error)
FindByID(ctx context.Context, discountID string) (*models.Discount, error)
FindAll(ctx context.Context) ([]models.Discount, error)
FindByUserID(ctx context.Context, userID string) ([]models.Discount, error)
Determine(ctx context.Context, conditions *core.DiscountConditions) ([]models.Discount, error)
UpdateOne(ctx context.Context, update *core.UpdateDiscountSettings) error
}
type DiscountServiceDeps struct {
Logger *logrus.Logger
DiscountRepository DiscountRepository
}
type DiscountService struct {
logger *logrus.Logger
discountRepository DiscountRepository
}
func NewDiscountService(deps *DiscountServiceDeps) *DiscountService {
return &DiscountService{
logger: deps.Logger,
discountRepository: deps.DiscountRepository,
}
}
func (receiver *DiscountService) ApplyDiscounts(ctx context.Context, request *discount.ApplyDiscountRequest) (*discount.ApplyDiscountResponse, error) {
conditions, err := utils.ConstituteConditions(request)
if err != nil {
receiver.logger.Errorf("failed to constitute conditions on <ApplyDiscounts> of <DiscountService>: %v", err)
return nil, ErrInvalidInputValue
}
discounts, err := receiver.discountRepository.Determine(ctx, conditions)
if err != nil {
receiver.logger.Errorf("failed to determine conditions on <ApplyDiscounts> of <DiscountService>: %v", err)
return nil, err
}
filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts(discounts)
if couponDiscount != nil {
deprecated := true
if err := receiver.discountRepository.UpdateOne(ctx, &core.UpdateDiscountSettings{
ID: couponDiscount.ID,
Deprecated: &deprecated,
}); err != nil {
receiver.logger.Errorf("failed to deprecate coupon discount on <ApplyDiscounts> of <DiscountService>: %v", err)
return nil, err
}
}
products := transfer.ProductsProtoToCore(request.Products)
return &discount.ApplyDiscountResponse{
Price: discountUtils.Calculate(products, filteredDiscounts),
AppliedDiscounts: transfer.DiscountsModelToProto(filteredDiscounts).Discounts,
}, nil
}
func (receiver *DiscountService) GetDiscountByID(ctx context.Context, id string) (*models.Discount, error) {
discount, err := receiver.discountRepository.FindByID(ctx, id)
if err != nil {
receiver.logger.Errorf("failed to get discount on <GetDiscountByID> of <DiscountService>: %v", err)
return nil, err
}
return discount, nil
}
func (receiver *DiscountService) GetDiscountsByUserID(ctx context.Context, id string) ([]models.Discount, error) {
discounts, err := receiver.discountRepository.FindByUserID(ctx, id)
if err != nil {
receiver.logger.Errorf("failed to get discount on <GetDiscountByUserID> of <DiscountService>: %v", err)
return nil, err
}
return discounts, nil
}
func (receiver *DiscountService) GetAllDiscounts(ctx context.Context) ([]models.Discount, error) {
discounts, err := receiver.discountRepository.FindAll(ctx)
if err != nil {
receiver.logger.Errorf("failed to get all discounts on <GetAllDiscounts> of <DiscountService>: %v", err)
return nil, err
}
return discounts, nil
}
func (receiver *DiscountService) DeleteDiscount(ctx context.Context, id string) (*models.Discount, error) {
deletedDiscount, err := receiver.discountRepository.DeleteByID(ctx, id)
if err != nil {
receiver.logger.Errorf("failed delete discount on <DeleteDiscount> of <DiscountService>: %v", err)
return nil, err
}
return deletedDiscount, nil
}
func (receiver *DiscountService) CreateDiscount(ctx context.Context, discount *models.Discount) (*models.Discount, error) {
createdDiscountID, err := receiver.discountRepository.Insert(ctx, discount)
if err != nil {
return nil, err
}
if createdDiscountID == "" {
receiver.logger.Errorf("empty discount ID on <CreateDiscount> of <DiscountService>")
return nil, ErrInvalidReturnValue
}
return &models.Discount{
Target: discount.Target,
Condition: discount.Condition,
ID: createdDiscountID,
Name: discount.Name,
Description: discount.Description,
Layer: discount.Layer,
}, nil
}

@ -1,8 +1,8 @@
package service
import "errors"
var (
ErrInvalidInputValue = errors.New("invalid input value in args of method")
ErrInvalidReturnValue = errors.New("invalid return value of function inside method")
)
package service
import "errors"
var (
ErrInvalidInputValue = errors.New("invalid input value in args of method")
ErrInvalidReturnValue = errors.New("invalid return value of function inside method")
)

@ -1,52 +0,0 @@
package calculate
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert"
)
/*
TODO:
1) Рефактор всех приватных утилит
2) Покрытие тестами всех утилит
3) Уточнить систему overhelm при высчитывании
*/
func Discount(products []core.Product, discounts []models.Discount) float64 {
if len(products) < 1 {
return 0
}
price := calculateProducts(products)
if len(discounts) < 1 {
return price
}
discountTargets := constituteDiscountTargets(divideDiscounts(discounts))
productsMap := convert.ArrayToMap(products, func(product core.Product) string {
return product.ID
})
if eachTarget, ok := discountTargets[models.TargetEach]; ok {
productsMap = calculateEachProduct(productsMap, eachTarget)
price = calculateProductsMap(productsMap)
}
if groupTarget, ok := discountTargets[models.TargetGroup]; ok {
productGroups := convert.MapToMapArray(productsMap, func(product core.Product) string {
return product.Group
})
pricesGroup := calculateGroupProducts(productGroups, groupTarget)
price = calculatePricesGroup(pricesGroup)
}
if sumTarget, ok := discountTargets[models.TargetSum]; ok {
price = price * sumTarget.Factor
}
return price
}

@ -1,94 +0,0 @@
package calculate
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
)
func calculateProducts(products []core.Product) float64 {
productsPrice := float64(0)
for _, product := range products {
productsPrice = productsPrice + product.Price
}
return productsPrice
}
func calculateProductsMap(products map[string]core.Product) float64 {
productsPrice := float64(0)
for _, product := range products {
productsPrice = productsPrice + product.Price
}
return productsPrice
}
func calculatePricesGroup(pricesGroup map[string]float64) float64 {
finalPrice := float64(0)
for _, price := range pricesGroup {
finalPrice = finalPrice + price
}
return finalPrice
}
func calculateEachProduct(products map[string]core.Product, target models.DiscountCalculationTarget) map[string]core.Product {
if target.Products == nil && target.Factor == 0 {
return products
}
if target.Products == nil {
for key, product := range products {
products[key] = core.Product{
ID: key,
Price: product.Price * target.Factor,
}
}
return products
}
for _, productTarget := range target.Products {
product, ok := products[productTarget.ID]
if !ok {
continue
}
if productTarget.Factor != 0 {
products[product.ID] = core.Product{
ID: product.ID,
Price: product.Price * productTarget.Factor,
}
continue
}
products[product.ID] = core.Product{
ID: product.ID,
Price: product.Price * target.Factor,
}
}
return products
}
func calculateGroupProducts(productsGroup map[string][]core.Product, target models.DiscountCalculationTarget) map[string]float64 {
pricesGroup := make(map[string]float64)
if target.TargetGroup == "" && target.Factor == 0 {
for group, products := range productsGroup {
pricesGroup[group] = calculateProducts(products)
}
return pricesGroup
}
for group, products := range productsGroup {
pricesGroup[group] = calculateProducts(products) * target.Factor
}
return pricesGroup
}

@ -1,92 +0,0 @@
package calculate
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert"
)
func constituteDiscountTargets(dividedDiscounts map[models.TargetScope][]models.Discount) map[models.TargetScope]models.DiscountCalculationTarget {
discountTargets := make(map[models.TargetScope]models.DiscountCalculationTarget, 3)
for group, discounts := range dividedDiscounts {
switch group {
case models.TargetEach:
discountTargets[group] = models.DiscountCalculationTarget{
Products: sumEachFactor(discounts),
}
case models.TargetGroup, models.TargetSum:
discountTargets[group] = models.DiscountCalculationTarget{
Factor: sumCommonFactor(discounts),
}
}
}
return discountTargets
}
func divideDiscounts(discounts []models.Discount) map[models.TargetScope][]models.Discount {
dividedDiscounts := make(map[models.TargetScope][]models.Discount, 3)
for _, discount := range discounts {
if len(discount.Target.Products) > 0 {
dividedDiscounts[models.TargetEach] = append(dividedDiscounts[models.TargetEach], discount)
continue
}
if discount.Target.TargetScope == "" && len(discount.Target.Products) < 1 {
dividedDiscounts[models.TargetSum] = append(dividedDiscounts[models.TargetSum], discount)
continue
}
if discount.Target.TargetScope == "" {
continue
}
dividedDiscounts[discount.Target.TargetScope] = append(dividedDiscounts[discount.Target.TargetScope], discount)
}
return dividedDiscounts
}
func sumCommonFactor(discounts []models.Discount) float64 {
sum := float64(1)
for _, discount := range discounts {
if discount.Target.Overhelm {
sum = discount.Target.Factor
break
}
sum = sum * discount.Target.Factor
}
return sum
}
func sumEachFactor(discounts []models.Discount) []models.ProductTarget {
productTargetsMap := make(map[string]models.ProductTarget)
for _, discount := range discounts {
for _, productTarget := range discount.Target.Products {
if productTargetsMap[productTarget.ID].Overhelm {
continue
}
if productTargetsMap[productTarget.ID].Factor > 0 {
productTargetsMap[productTarget.ID] = models.ProductTarget{
ID: productTarget.ID,
Factor: productTargetsMap[productTarget.ID].Factor * productTarget.Factor,
Overhelm: productTargetsMap[productTarget.ID].Overhelm,
}
continue
}
productTargetsMap[productTarget.ID] = models.ProductTarget{
ID: productTarget.ID,
Factor: 1 * productTarget.Factor,
Overhelm: productTargetsMap[productTarget.ID].Overhelm,
}
}
}
return convert.MapToArray(productTargetsMap)
}

@ -1,58 +1,58 @@
package utils
import (
"errors"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
func ConstituteConditions(request *discount.ApplyDiscountRequest) (*core.DiscountConditions, error) {
if request == nil {
return nil, nil
}
if len(request.GetProducts()) < 1 {
return nil, nil
}
optionalConditions := make([]core.OptionalDiscounCondition, len(request.GetProducts())*2)
optionalConditionsIndex := 0
for _, product := range request.Products {
if product == nil {
return nil, errors.New("some of products are nil")
}
optionalConditions[optionalConditionsIndex] = core.OptionalDiscounCondition{
Product: &product.ID,
PriceFrom: &product.Price,
Group: product.Group,
Term: product.Term,
Usage: product.Usage,
}
optionalConditions[optionalConditionsIndex+1] = core.OptionalDiscounCondition{
Product: &product.ID,
Group: product.Group,
Term: product.Term,
Usage: product.Usage,
}
optionalConditionsIndex = optionalConditionsIndex + 2
}
return &core.DiscountConditions{
Common: core.CommonDiscountCondition{
User: request.UserInformation.ID,
UserType: request.UserInformation.Type,
PurchasesAmount: request.UserInformation.PurchasesAmount,
CartPurchasesAmount: request.UserInformation.CartPurchasesAmount,
Coupon: request.Coupon,
Period: models.PeriodCondition{
From: request.Date.AsTime(),
To: request.Date.AsTime(),
},
},
Optionals: optionalConditions,
}, nil
}
package utils
import (
"errors"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
func ConstituteConditions(request *discount.ApplyDiscountRequest) (*core.DiscountConditions, error) {
if request == nil {
return nil, nil
}
if len(request.GetProducts()) < 1 {
return nil, nil
}
optionalConditions := make([]core.OptionalDiscounCondition, len(request.GetProducts())*2)
optionalConditionsIndex := 0
for _, product := range request.Products {
if product == nil {
return nil, errors.New("some of products are nil")
}
optionalConditions[optionalConditionsIndex] = core.OptionalDiscounCondition{
Product: &product.ID,
PriceFrom: &product.Price,
Group: product.Group,
Term: product.Term,
Usage: product.Usage,
}
optionalConditions[optionalConditionsIndex+1] = core.OptionalDiscounCondition{
Product: &product.ID,
Group: product.Group,
Term: product.Term,
Usage: product.Usage,
}
optionalConditionsIndex += 2
}
return &core.DiscountConditions{
Common: core.CommonDiscountCondition{
User: request.UserInformation.ID,
UserType: request.UserInformation.Type,
PurchasesAmount: request.UserInformation.PurchasesAmount,
CartPurchasesAmount: request.UserInformation.CartPurchasesAmount,
Coupon: request.Coupon,
Period: models.PeriodCondition{
From: request.Date.AsTime(),
To: request.Date.AsTime(),
},
},
Optionals: optionalConditions,
}, nil
}

@ -1,151 +1,150 @@
package utils_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils"
)
func TestConstituteConditions(t *testing.T) {
userID := "1"
userType := "nkvo"
purchasesAmount := float64(100000)
cartPurchasesAmount := float64(5000)
productsGroup := "group"
productID1 := "1"
productID2 := "2"
productID3 := "3"
productID4 := "4"
productPrice1 := float64(1000)
productPrice2 := float64(6000)
productPrice3 := float64(10000)
productPrice4 := float64(4000)
productTerm1 := uint64(14)
productTerm2 := uint64(16)
productUsage1 := uint64(24)
productUsage2 := uint64(14)
coupon := "coupon"
period := models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
}
t.Run("Формирование условий поиска с пустыми значениями", func(t *testing.T) {
conditions, _ := utils.ConstituteConditions(&discount.ApplyDiscountRequest{})
assert.Nil(t, conditions)
})
t.Run("Формирование условий поиска с nil", func(t *testing.T) {
conditions, _ := utils.ConstituteConditions(nil)
assert.Nil(t, conditions)
})
t.Run("Формирование условий поиска с заполненной информацией", func(t *testing.T) {
conditions, err := utils.ConstituteConditions(&discount.ApplyDiscountRequest{
UserInformation: &discount.UserInformation{
ID: userID,
Type: userType,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
},
Products: []*discount.ProductInformation{
{ID: productID1, Price: productPrice1, Term: &productTerm1, Group: &productsGroup},
{ID: productID2, Price: productPrice2, Term: &productTerm2, Group: &productsGroup},
{ID: productID3, Price: productPrice3, Usage: &productUsage1},
{ID: productID4, Price: productPrice4, Term: &productTerm2, Usage: &productUsage2},
},
Coupon: &coupon,
Date: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
})
assert.Nil(t, err)
assert.Equal(t, &core.DiscountConditions{
Common: core.CommonDiscountCondition{
User: userID,
UserType: userType,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
Coupon: &coupon,
Period: period,
},
Optionals: []core.OptionalDiscounCondition{
{
Product: &productID1,
PriceFrom: &productPrice1,
Group: &productsGroup,
Term: &productTerm1,
},
{
Product: &productID1,
Group: &productsGroup,
Term: &productTerm1,
},
{
Product: &productID2,
PriceFrom: &productPrice2,
Group: &productsGroup,
Term: &productTerm2,
},
{
Product: &productID2,
Group: &productsGroup,
Term: &productTerm2,
},
{
Product: &productID3,
PriceFrom: &productPrice3,
Usage: &productUsage1,
},
{
Product: &productID3,
Usage: &productUsage1,
},
{
Product: &productID4,
PriceFrom: &productPrice4,
Term: &productTerm2,
Usage: &productUsage2,
},
{
Product: &productID4,
Term: &productTerm2,
Usage: &productUsage2,
},
},
}, conditions)
})
t.Run("Формирование условий поиска с наличием nil'ого продукта", func(t *testing.T) {
conditions, err := utils.ConstituteConditions(&discount.ApplyDiscountRequest{
UserInformation: &discount.UserInformation{
ID: userID,
Type: userType,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
},
Products: []*discount.ProductInformation{
{ID: productID1, Price: productPrice1, Term: &productTerm1, Group: &productsGroup},
nil,
},
Date: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
})
assert.Nil(t, conditions)
assert.Error(t, err)
})
}
package utils_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils"
)
func TestConstituteConditions(t *testing.T) {
userID := "1"
userType := "nkvo"
purchasesAmount := uint64(100000)
cartPurchasesAmount := uint64(5000)
productsGroup := "group"
productID1 := "1"
productID2 := "2"
productID3 := "3"
productID4 := "4"
productPrice1 := uint64(1000)
productPrice2 := uint64(6000)
productPrice3 := uint64(10000)
productPrice4 := uint64(4000)
productTerm1 := uint64(14)
productTerm2 := uint64(16)
productUsage1 := uint64(24)
productUsage2 := uint64(14)
coupon := "coupon"
period := models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
}
t.Run("Формирование условий поиска с пустыми значениями", func(t *testing.T) {
conditions, _ := utils.ConstituteConditions(&discount.ApplyDiscountRequest{})
assert.Nil(t, conditions)
})
t.Run("Формирование условий поиска с nil", func(t *testing.T) {
conditions, _ := utils.ConstituteConditions(nil)
assert.Nil(t, conditions)
})
t.Run("Формирование условий поиска с заполненной информацией", func(t *testing.T) {
conditions, err := utils.ConstituteConditions(&discount.ApplyDiscountRequest{
UserInformation: &discount.UserInformation{
ID: userID,
Type: userType,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
},
Products: []*discount.ProductInformation{
{ID: productID1, Price: productPrice1, Term: &productTerm1, Group: &productsGroup},
{ID: productID2, Price: productPrice2, Term: &productTerm2, Group: &productsGroup},
{ID: productID3, Price: productPrice3, Usage: &productUsage1},
{ID: productID4, Price: productPrice4, Term: &productTerm2, Usage: &productUsage2},
},
Coupon: &coupon,
Date: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
})
assert.Nil(t, err)
assert.Equal(t, &core.DiscountConditions{
Common: core.CommonDiscountCondition{
User: userID,
UserType: userType,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
Coupon: &coupon,
Period: period,
},
Optionals: []core.OptionalDiscounCondition{
{
Product: &productID1,
PriceFrom: &productPrice1,
Group: &productsGroup,
Term: &productTerm1,
},
{
Product: &productID1,
Group: &productsGroup,
Term: &productTerm1,
},
{
Product: &productID2,
PriceFrom: &productPrice2,
Group: &productsGroup,
Term: &productTerm2,
},
{
Product: &productID2,
Group: &productsGroup,
Term: &productTerm2,
},
{
Product: &productID3,
PriceFrom: &productPrice3,
Usage: &productUsage1,
},
{
Product: &productID3,
Usage: &productUsage1,
},
{
Product: &productID4,
PriceFrom: &productPrice4,
Term: &productTerm2,
Usage: &productUsage2,
},
{
Product: &productID4,
Term: &productTerm2,
Usage: &productUsage2,
},
},
}, conditions)
})
t.Run("Формирование условий поиска с наличием nil'ого продукта", func(t *testing.T) {
conditions, err := utils.ConstituteConditions(&discount.ApplyDiscountRequest{
UserInformation: &discount.UserInformation{
ID: userID,
Type: userType,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
},
Products: []*discount.ProductInformation{
{ID: productID1, Price: productPrice1, Term: &productTerm1, Group: &productsGroup},
nil,
},
Date: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
})
assert.Nil(t, conditions)
assert.Error(t, err)
})
}

@ -0,0 +1,144 @@
package discount
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert"
)
/*
TODO:
1) Рефактор всех приватных утилит
2) Покрытие тестами всех утилит
*/
func Calculate(products []core.Product, discounts []models.Discount) uint64 {
if len(products) < 1 {
return 0
}
price := calculateProducts(products)
if len(discounts) < 1 {
return convert.FloatToUint64(price)
}
discountTargetsMap := SortOverhelmingDiscounts(discounts)
productsMap := convert.ArrayToMap(products, func(product core.Product) string {
return product.ID
})
if targetsEach, ok := discountTargetsMap[models.TargetEach]; ok {
for _, target := range targetsEach {
productsMap = calculateEachProductWithTarget(productsMap, target)
}
price = calculateProductsMap(productsMap)
}
if targetsGroup, ok := discountTargetsMap[models.TargetGroup]; ok {
productGroups := convert.MapToMapArray(productsMap, func(product core.Product) string {
return product.Group
})
pricesGroup := calculateProductsGroup(productGroups)
for _, target := range targetsGroup {
pricesGroup[target.TargetGroup] *= target.Factor
}
price = calculatePricesGroupSum(pricesGroup)
}
if targetsSum, ok := discountTargetsMap[models.TargetSum]; ok {
for _, target := range targetsSum {
price *= target.Factor
}
}
return convert.FloatToUint64(price)
}
func calculateProducts(products []core.Product) float64 {
productsPrice := float64(0)
for _, product := range products {
productsPrice += product.Price
}
return productsPrice
}
func calculateProductsMap(products map[string]core.Product) float64 {
productsPrice := float64(0)
for _, product := range products {
productsPrice += product.Price
}
return productsPrice
}
func calculatePricesGroupSum(pricesGroup map[string]float64) float64 {
finalPrice := float64(0)
for _, price := range pricesGroup {
finalPrice += price
}
return finalPrice
}
func calculateEachProductWithTarget(products map[string]core.Product, target models.DiscountCalculationTarget) map[string]core.Product {
if target.Products == nil && target.Factor == 0 {
return products
}
if target.Products == nil {
for key, product := range products {
products[key] = core.Product{
ID: key,
Price: product.Price * target.Factor,
Group: product.Group,
}
}
return products
}
for _, productTarget := range target.Products {
product, ok := products[productTarget.ID]
if !ok {
continue
}
if productTarget.Factor != 0 {
products[product.ID] = core.Product{
ID: product.ID,
Price: product.Price * productTarget.Factor,
Group: product.Group,
}
continue
}
products[product.ID] = core.Product{
ID: product.ID,
Price: product.Price * target.Factor,
Group: product.Group,
}
}
return products
}
func calculateProductsGroup(productsGroup map[string][]core.Product) map[string]float64 {
pricesGroup := make(map[string]float64)
for group, products := range productsGroup {
pricesGroup[group] = calculateProducts(products)
}
return pricesGroup
}

@ -1,344 +1,346 @@
package calculate_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/calculate"
)
func TestCalculate(t *testing.T) {
t.Run("Покупка продуктов в количестве не больше 1 при отсутствии скидок", func(t *testing.T) {
assert.Equal(t, 3.0, calculate.Discount(
[]core.Product{
{ID: "p1", Price: 1.0},
{ID: "p2", Price: 2.0},
},
[]models.Discount{},
))
})
t.Run("Сумма корзины достигла 5к", func(t *testing.T) {
assert.Equal(t, 4925.0, calculate.Discount(
[]core.Product{
{ID: "p1", Price: 2000.0},
{ID: "p2", Price: 3000.0},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.985,
},
},
},
))
assert.Equal(t, 4925.0, calculate.Discount(
[]core.Product{
{ID: "p1", Price: 2000.0},
{ID: "p2", Price: 3000.0},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Factor: 0.985,
},
},
},
))
})
t.Run("Наложение несколько скидок разного формата", func(t *testing.T) {
assert.InEpsilon(t, 5025.32, calculate.Discount(
[]core.Product{
{ID: "p1", Price: 2000.0},
{ID: "p2", Price: 3000.0},
{ID: "p3", Price: 105.0},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{{ID: "p3", Factor: 0.97}},
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.985,
},
},
},
), 0.001)
})
t.Run("Наложение несколько скидок разного формата с несколькими таргетами", func(t *testing.T) {
assert.InEpsilon(t, 5223.89, calculate.Discount(
[]core.Product{
{ID: "p1", Price: 2000.0},
{ID: "p2", Price: 3000.0},
{ID: "p3", Price: 105.0},
{ID: "p4", Price: 210.0},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p3", Factor: 0.97},
{ID: "p4", Factor: 0.96},
},
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.985,
},
},
},
), 0.001)
})
t.Run("Наложение несколько скидок с наличием скидки на лояльность", func(t *testing.T) {
assert.InEpsilon(t, 5171.66, calculate.Discount(
[]core.Product{
{ID: "p1", Price: 2000.0},
{ID: "p2", Price: 3000.0},
{ID: "p3", Price: 105.0},
{ID: "p4", Price: 210.0},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p3", Factor: 0.97},
{ID: "p4", Factor: 0.96},
},
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.985,
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.99,
},
},
},
), 0.001)
})
t.Run("Наложение несколько скидок на привелегии", func(t *testing.T) {
assert.InEpsilon(t, 422.28, calculate.Discount(
[]core.Product{
{ID: "p5", Price: 30},
{ID: "p6", Price: 70},
{ID: "p7", Price: 400},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.935},
},
},
},
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.83},
},
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.99,
},
},
},
), 0.001)
})
t.Run("Наложение несколько скидок на привелегии без лояльности", func(t *testing.T) {
assert.InEpsilon(t, 426.55, calculate.Discount(
[]core.Product{
{ID: "p5", Price: 30},
{ID: "p6", Price: 70},
{ID: "p7", Price: 400},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.935},
{ID: "p7", Factor: 0.83},
},
},
},
},
), 0.001)
})
t.Run("Вычисление скидки за привелегии и сервис", func(t *testing.T) {
assert.InEpsilon(t, 743.45, calculate.Discount(
[]core.Product{
{ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.935},
{ID: "p7", Factor: 0.83},
},
},
},
{
Target: models.DiscountCalculationTarget{
Factor: 0.99,
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "dwarfener",
Factor: 0.99,
},
},
},
), 0.001)
})
t.Run("Наложение скидок на несколько сервисов", func(t *testing.T) {
assert.InEpsilon(t, 2398.5, calculate.Discount(
[]core.Product{
{ID: "p1", Price: 1500, Group: "templategen"},
{ID: "p2", Price: 300, Group: "templategen"},
{ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.935},
{ID: "p7", Factor: 0.83},
},
},
},
{
Target: models.DiscountCalculationTarget{
Factor: 0.99,
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "templategen",
Factor: 0.996,
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "dwarfener",
Factor: 0.99,
},
},
},
), 0.01)
})
t.Run("Наложение скидок с промокодом", func(t *testing.T) {
assert.InEpsilon(t, 2368.68, calculate.Discount(
[]core.Product{
{ID: "p1", Price: 1500, Group: "templategen"},
{ID: "p2", Price: 300, Group: "templategen"},
{ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p6", Factor: 0.5, Overhelm: true},
},
},
},
{
Target: models.DiscountCalculationTarget{
Factor: 0.94,
},
},
{
Target: models.DiscountCalculationTarget{
Factor: 0.99,
Overhelm: true,
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "templategen",
Factor: 0.996,
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "dwarfener",
Factor: 0.99,
},
},
},
), 0.01)
})
t.Run("Наложение скидок по типу пользователя", func(t *testing.T) {
assert.InEpsilon(t, 540, calculate.Discount(
[]core.Product{
{ID: "p1", Price: 1500, Group: "templategen"},
{ID: "p2", Price: 300, Group: "templategen"},
{ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.2,
},
},
},
), 0.01)
})
}
package discount_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/discount"
)
func TestCalculate(t *testing.T) {
t.Run("Покупка продуктов в количестве не больше 1 при отсутствии скидок", func(t *testing.T) {
assert.Equal(t, uint64(3), discount.Calculate(
[]core.Product{
{ID: "p1", Price: 1},
{ID: "p2", Price: 2},
},
[]models.Discount{},
))
})
t.Run("Сумма корзины достигла 5к", func(t *testing.T) {
assert.Equal(t, uint64(4925), discount.Calculate(
[]core.Product{
{ID: "p1", Price: 2000},
{ID: "p2", Price: 3000},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.985,
},
},
},
))
assert.Equal(t, uint64(4925), discount.Calculate(
[]core.Product{
{ID: "p1", Price: 2000},
{ID: "p2", Price: 3000},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Factor: 0.985,
},
},
},
))
})
t.Run("Наложение несколько скидок разного формата", func(t *testing.T) {
assert.Equal(t, uint64(5025), discount.Calculate(
[]core.Product{
{ID: "p1", Price: 2000},
{ID: "p2", Price: 3000},
{ID: "p3", Price: 105},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{{ID: "p3", Factor: 0.97}},
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.985,
},
},
},
))
})
t.Run("Наложение несколько скидок разного формата с несколькими таргетами", func(t *testing.T) {
assert.Equal(t, uint64(5223), discount.Calculate(
[]core.Product{
{ID: "p1", Price: 2000},
{ID: "p2", Price: 3000},
{ID: "p3", Price: 105},
{ID: "p4", Price: 210},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p3", Factor: 0.97},
{ID: "p4", Factor: 0.96},
},
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.985,
},
},
},
))
})
t.Run("Наложение несколько скидок с наличием скидки на лояльность", func(t *testing.T) {
assert.Equal(t, uint64(5171), discount.Calculate(
[]core.Product{
{ID: "p1", Price: 2000},
{ID: "p2", Price: 3000},
{ID: "p3", Price: 105},
{ID: "p4", Price: 210},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p3", Factor: 0.97},
{ID: "p4", Factor: 0.96},
},
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.985,
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.99,
},
},
},
), 0.001)
})
t.Run("Наложение несколько скидок на привелегии", func(t *testing.T) {
assert.Equal(t, uint64(422), discount.Calculate(
[]core.Product{
{ID: "p5", Price: 30},
{ID: "p6", Price: 70},
{ID: "p7", Price: 400},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.935},
},
},
},
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.83},
},
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.99,
},
},
},
))
})
t.Run("Наложение несколько скидок на привелегии без лояльности", func(t *testing.T) {
assert.Equal(t, uint64(426), discount.Calculate(
[]core.Product{
{ID: "p5", Price: 30},
{ID: "p6", Price: 70},
{ID: "p7", Price: 400},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.935},
{ID: "p7", Factor: 0.83},
},
},
},
},
))
})
t.Run("Вычисление скидки за привелегии и сервис", func(t *testing.T) {
assert.Equal(t, uint64(743), discount.Calculate(
[]core.Product{
{ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.935},
{ID: "p7", Factor: 0.83},
},
},
},
{
Target: models.DiscountCalculationTarget{
Factor: 0.99,
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "dwarfener",
Factor: 0.99,
},
},
},
))
})
t.Run("Наложение скидок на несколько сервисов", func(t *testing.T) {
assert.Equal(t, uint64(2398), discount.Calculate(
[]core.Product{
{ID: "p1", Price: 1500, Group: "templategen"},
{ID: "p2", Price: 300, Group: "templategen"},
{ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.935},
{ID: "p7", Factor: 0.83},
},
},
},
{
Target: models.DiscountCalculationTarget{
Factor: 0.99,
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "templategen",
Factor: 0.996,
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "dwarfener",
Factor: 0.99,
},
},
},
))
})
t.Run("Наложение скидок с промокодом", func(t *testing.T) {
assert.Equal(t, uint64(2368), discount.Calculate(
[]core.Product{
{ID: "p1", Price: 1500, Group: "templategen"},
{ID: "p2", Price: 300, Group: "templategen"},
{ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
{
Layer: 2,
Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{
{ID: "p6", Factor: 0.5, Overhelm: true},
},
},
},
{
Target: models.DiscountCalculationTarget{
Factor: 0.94,
},
},
{
ID: "2",
Layer: 2,
Target: models.DiscountCalculationTarget{
Factor: 0.99,
Overhelm: true,
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "templategen",
Factor: 0.996,
},
},
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "dwarfener",
Factor: 0.99,
},
},
},
))
})
t.Run("Наложение скидок по типу пользователя", func(t *testing.T) {
assert.Equal(t, uint64(540), discount.Calculate(
[]core.Product{
{ID: "p1", Price: 1500, Group: "templategen"},
{ID: "p2", Price: 300, Group: "templategen"},
{ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"},
},
[]models.Discount{
{
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.2,
},
},
},
))
})
}

@ -0,0 +1,147 @@
package discount
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
)
func SortOverhelmingDiscounts(discounts []models.Discount) map[models.TargetScope][]models.DiscountCalculationTarget {
dividedDiscounts := divideDiscountsByScope(discounts)
return map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: sortOverhelmingDiscountsEach(dividedDiscounts[models.TargetEach]),
models.TargetGroup: sortOverhelmingDiscounts(dividedDiscounts[models.TargetGroup]),
models.TargetSum: sortOverhelmingDiscounts(dividedDiscounts[models.TargetSum]),
}
}
func constituteOverhelmingProductTargetLayersMap(discounts []models.Discount) map[string]uint32 {
overhelmingProductTargetLayers := make(map[string]uint32)
for _, discount := range discounts {
for _, productTarget := range discount.Target.Products {
if existingLayer, ok := overhelmingProductTargetLayers[productTarget.ID]; ok {
if productTarget.Overhelm && existingLayer < discount.Layer {
overhelmingProductTargetLayers[productTarget.ID] = discount.Layer
}
continue
}
if productTarget.Overhelm {
overhelmingProductTargetLayers[productTarget.ID] = discount.Layer
}
}
}
return overhelmingProductTargetLayers
}
func constituteOverhelmingDiscountLayer(discounts []models.Discount) uint32 {
overhelmingDiscountLayer := uint32(0)
for _, discount := range discounts {
if discount.Target.Overhelm && overhelmingDiscountLayer < discount.Layer {
overhelmingDiscountLayer = discount.Layer
}
}
return overhelmingDiscountLayer
}
func sortOverhelmingDiscountsEach(discounts []models.Discount) []models.DiscountCalculationTarget {
if len(discounts) < 1 {
return []models.DiscountCalculationTarget{}
}
sortedDiscountCalculationTargetsEach := make([]models.DiscountCalculationTarget, 0, len(discounts))
overhelmingProductTargetLayers := constituteOverhelmingProductTargetLayersMap(discounts)
overhelmingDiscountLayer := constituteOverhelmingDiscountLayer(discounts)
for _, discount := range discounts {
if overhelmingDiscountLayer > discount.Layer {
continue
}
updatedProductTargets := make([]models.ProductTarget, 0, len(discount.Target.Products))
for _, productTarget := range discount.Target.Products {
if overhelmingProductTarget, ok := overhelmingProductTargetLayers[productTarget.ID]; ok && overhelmingProductTarget > discount.Layer {
continue
}
updatedProductTargets = append(updatedProductTargets, models.ProductTarget{
ID: productTarget.ID,
Factor: productTarget.Factor,
})
}
sortedDiscountCalculationTargetsEach = append(sortedDiscountCalculationTargetsEach, models.DiscountCalculationTarget{
Products: updatedProductTargets,
Factor: discount.Target.Factor,
TargetScope: discount.Target.TargetScope,
TargetGroup: discount.Target.TargetGroup,
})
}
return sortedDiscountCalculationTargetsEach
}
func sortOverhelmingDiscounts(discounts []models.Discount) []models.DiscountCalculationTarget {
if len(discounts) < 1 {
return []models.DiscountCalculationTarget{}
}
sortedDiscountCalculationTargets := make([]models.DiscountCalculationTarget, 0, len(discounts))
overhelmingDiscountLayer := constituteOverhelmingDiscountLayer(discounts)
for _, discount := range discounts {
if overhelmingDiscountLayer > discount.Layer {
continue
}
sortedDiscountCalculationTargets = append(sortedDiscountCalculationTargets, models.DiscountCalculationTarget{
Factor: discount.Target.Factor,
TargetScope: discount.Target.TargetScope,
TargetGroup: discount.Target.TargetGroup,
})
}
return sortedDiscountCalculationTargets
}
func divideDiscountsByScope(discounts []models.Discount) map[models.TargetScope][]models.Discount {
dividedTargets := make(map[models.TargetScope][]models.Discount, 3)
for _, discount := range discounts {
if discount.Target.TargetScope == "" && len(discount.Target.Products) > 0 {
dividedTargets[models.TargetEach] = append(dividedTargets[models.TargetEach], models.Discount{
Layer: discount.Layer,
Target: models.DiscountCalculationTarget{
Products: discount.Target.Products,
Factor: discount.Target.Factor,
TargetScope: models.TargetEach,
Overhelm: discount.Target.Overhelm,
},
})
continue
}
if discount.Target.TargetScope == "" && len(discount.Target.Products) < 1 {
dividedTargets[models.TargetSum] = append(dividedTargets[models.TargetSum], models.Discount{
Layer: discount.Layer,
Target: models.DiscountCalculationTarget{
Factor: discount.Target.Factor,
TargetScope: models.TargetSum,
Overhelm: discount.Target.Overhelm,
},
})
continue
}
dividedTargets[discount.Target.TargetScope] = append(dividedTargets[discount.Target.TargetScope], discount)
}
return dividedTargets
}

@ -0,0 +1,743 @@
package discount_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/discount"
)
func TestSortOverhelmingDiscounts(t *testing.T) {
t.Run("Успешная сортировка скидок по каждому продукту при наличии перекрывающией скидки на каждый продукт и на определённый продукт", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
{
TargetScope: models.TargetEach,
Factor: 0.8,
Products: []models.ProductTarget{
{ID: "p6", Factor: 0.5},
},
},
},
models.TargetGroup: {},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "1",
Layer: 1,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.94},
},
},
},
{
ID: "2",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91},
},
},
},
{
ID: "3",
Layer: 3,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
{
ID: "4",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.8,
Products: []models.ProductTarget{
{ID: "p6", Factor: 0.5, Overhelm: true},
},
},
},
}),
)
})
t.Run("Успешная сортировка скидок по каждому продукту при наличии самой перекрывающей скидки", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
},
models.TargetGroup: {},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "5",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
},
{
ID: "6",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91, Overhelm: true},
},
},
},
{
ID: "7",
Layer: 3,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
{
ID: "8",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.8,
Products: []models.ProductTarget{
{ID: "p6", Factor: 0.5, Overhelm: true},
},
},
},
}),
)
})
t.Run("Успешная сортировка скидок по каждому продукту при наличии скидок с одинаковым значением слоя", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91},
},
},
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
models.TargetGroup: {},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "9",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
},
{
ID: "1",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91, Overhelm: true},
},
},
},
{
ID: "2",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
{
ID: "3",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.8,
Products: []models.ProductTarget{
{ID: "p6", Factor: 0.5, Overhelm: true},
},
},
},
}),
)
})
t.Run("Успешная сортировка скидок по каждому продукту при наличии скидок с одинаковым значением слоя и полностью перекрывающими другие", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {
{
Factor: 0.92,
TargetScope: models.TargetEach,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
{
Factor: 0.92,
TargetScope: models.TargetEach,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91},
},
},
},
models.TargetGroup: {},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "4",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
},
{
ID: "7",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91, Overhelm: true},
},
},
},
{
ID: "4",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.8,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.5, Overhelm: true},
},
},
},
}),
)
})
t.Run("Успешная сортировка скидок по каждому продукту при наличии скидок, где одна полностью перекрывает продукты другой", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {
{
TargetScope: models.TargetEach,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
{
Factor: 0.92,
TargetScope: models.TargetEach,
Products: []models.ProductTarget{},
},
},
models.TargetGroup: {},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "5",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94, Overhelm: true},
},
},
},
{
ID: "6",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.5, Overhelm: true},
},
},
},
}),
)
})
t.Run("Успешная сортировка скидок по группе продуктов при наличии перекрывающих скидок", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {},
models.TargetGroup: {
{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.92,
},
},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "7",
Layer: 6,
Target: models.DiscountCalculationTarget{
Overhelm: true,
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.92,
},
},
{
ID: "8",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.96,
},
},
{
ID: "9",
Layer: 3,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.91,
},
},
}),
)
})
t.Run("Успешная сортировка скидок по группе продуктов при наличии перекрывающих скидок и разных групп", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {},
models.TargetGroup: {
{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.92,
},
},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "1",
Layer: 6,
Target: models.DiscountCalculationTarget{
Overhelm: true,
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.92,
},
},
{
ID: "2",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.42,
},
},
{
ID: "3",
Layer: 5,
Target: models.DiscountCalculationTarget{
Overhelm: true,
TargetScope: models.TargetGroup,
TargetGroup: "111",
Factor: 0.9,
},
},
{
ID: "4",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "111",
Factor: 0.8,
},
},
}),
)
})
t.Run("Успешная сортировка скидок по сумме при наличии скидки, перекрывающая все остальные", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {},
models.TargetGroup: {},
models.TargetSum: {
{
TargetScope: models.TargetSum,
Factor: 0.92,
},
},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "1",
Layer: 6,
Target: models.DiscountCalculationTarget{
Overhelm: true,
TargetScope: models.TargetSum,
Factor: 0.92,
},
},
{
ID: "2",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.42,
},
},
{
ID: "3",
Layer: 5,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Overhelm: true,
Factor: 0.9,
},
},
{
ID: "4",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.8,
},
},
}),
)
})
t.Run("Успешная сортировка скидок по сумме при наличии перекрывающей скидки", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {},
models.TargetGroup: {},
models.TargetSum: {
{
TargetScope: models.TargetSum,
Factor: 0.92,
},
{
TargetScope: models.TargetSum,
Factor: 0.42,
},
{
TargetScope: models.TargetSum,
Factor: 0.9,
},
},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "1",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.92,
},
},
{
ID: "2",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Overhelm: true,
Factor: 0.42,
},
},
{
ID: "3",
Layer: 5,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.9,
},
},
{
ID: "4",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.8,
},
},
}),
)
})
t.Run("Успешная сортировка смешанных скидок при наличии перекрывающих", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91},
},
},
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
models.TargetGroup: {
{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.92,
},
},
models.TargetSum: {
{
TargetScope: models.TargetSum,
Factor: 0.92,
},
{
TargetScope: models.TargetSum,
Factor: 0.42,
},
{
TargetScope: models.TargetSum,
Factor: 0.9,
},
},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "1",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.92,
},
},
{
ID: "2",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Overhelm: true,
Factor: 0.42,
},
},
{
ID: "3",
Layer: 5,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.9,
},
},
{
ID: "4",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.8,
},
},
{
ID: "5",
Layer: 6,
Target: models.DiscountCalculationTarget{
Overhelm: true,
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.92,
},
},
{
ID: "6",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.42,
},
},
{
ID: "7",
Layer: 5,
Target: models.DiscountCalculationTarget{
Overhelm: true,
TargetScope: models.TargetGroup,
TargetGroup: "111",
Factor: 0.9,
},
},
{
ID: "8",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "111",
Factor: 0.8,
},
},
{
ID: "9",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
},
{
ID: "10",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91, Overhelm: true},
},
},
},
{
ID: "11",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
{
ID: "12",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.8,
Products: []models.ProductTarget{
{ID: "p6", Factor: 0.5, Overhelm: true},
},
},
},
}),
)
})
}

@ -1,45 +1,44 @@
package expression
import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"golang.org/x/exp/constraints"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
)
type PeriodFieldPaths struct {
From string
To string
}
func GetAscRangeConditionBSON[T constraints.Ordered](key string, value T) []bson.D {
return []bson.D{
{{Key: key, Value: bson.M{"$lte": value}}},
{{Key: key, Value: nil}},
}
}
func GetValueConditionBSON(key string, value any) []bson.D {
return []bson.D{
{{Key: key, Value: value}},
{{Key: key, Value: nil}},
}
}
func GetPerionConditionBSON(period models.PeriodCondition, paths PeriodFieldPaths) []bson.M {
return []bson.M{
{
"$and": []bson.D{
{{Key: paths.From, Value: bson.M{"$lte": primitive.NewDateTimeFromTime(period.From)}}},
{{Key: paths.To, Value: bson.M{"$gte": primitive.NewDateTimeFromTime(period.To)}}},
},
},
{
"$and": []bson.D{
{{Key: paths.From, Value: nil}},
{{Key: paths.To, Value: nil}},
},
},
}
}
package expression
import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"golang.org/x/exp/constraints"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
)
type PeriodFieldPaths struct {
From string
To string
}
func GetAscRangeConditionBSON[T constraints.Ordered](key string, value T) []bson.D {
return []bson.D{
{{Key: key, Value: bson.M{"$lte": value}}},
{{Key: key, Value: nil}},
}
}
func GetValueConditionBSON(key string, value any) []bson.D {
return []bson.D{
{{Key: key, Value: value}},
{{Key: key, Value: nil}},
}
}
func GetPerionConditionBSON(period models.PeriodCondition, paths PeriodFieldPaths) []bson.M {
return []bson.M{
{
"$and": []bson.D{
{{Key: paths.From, Value: bson.M{"$lte": primitive.NewDateTimeFromTime(period.From)}}},
{{Key: paths.To, Value: bson.M{"$gte": primitive.NewDateTimeFromTime(period.To)}}},
},
},
{
"$and": []bson.D{
{{Key: paths.From, Value: nil}},
{{Key: paths.To, Value: nil}},
},
},
}
}

@ -1,84 +1,82 @@
package expression_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"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"
)
func TestGetAscRangeConditionBSON(t *testing.T) {
t.Run("Получение bson условия диапазона от меньшего с заполненными данными", func(t *testing.T) {
assert.Equal(t,
[]bson.D{
{{Key: "test", Value: bson.M{"$lte": "2020"}}},
{{Key: "test", Value: nil}},
},
expression.GetAscRangeConditionBSON("test", "2020"),
)
assert.Equal(t,
[]bson.D{
{{Key: "test2", Value: bson.M{"$lte": 200.00}}},
{{Key: "test2", Value: nil}},
},
expression.GetAscRangeConditionBSON("test2", 200.00),
)
})
}
func TestGetValueConditionBSON(t *testing.T) {
t.Run("Получение bson условия значения с заполненными данными", func(t *testing.T) {
assert.Equal(t,
[]bson.D{
{{Key: "test", Value: "2020"}},
{{Key: "test", Value: nil}},
},
expression.GetValueConditionBSON("test", "2020"),
)
assert.Equal(t,
[]bson.D{
{{Key: "test2", Value: 200.00}},
{{Key: "test2", Value: nil}},
},
expression.GetValueConditionBSON("test2", 200.00),
)
})
}
func TestGetPerionConditionBSON(t *testing.T) {
t.Run("Получение bson условия периода с заполненными данными", func(t *testing.T) {
assert.Equal(t,
[]bson.M{
{
"$and": []bson.D{
{{Key: fields.PeriodCondition.From, Value: bson.M{"$lte": primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC())}}},
{{Key: fields.PeriodCondition.To, Value: bson.M{"$gte": primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC())}}},
},
},
{
"$and": []bson.D{
{{Key: fields.PeriodCondition.From, Value: nil}},
{{Key: fields.PeriodCondition.To, Value: nil}},
},
},
},
expression.GetPerionConditionBSON(
models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
expression.PeriodFieldPaths{
From: fields.PeriodCondition.From,
To: fields.PeriodCondition.To,
},
),
)
})
}
package expression_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"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"
)
func TestGetAscRangeConditionBSON(t *testing.T) {
t.Run("Получение bson условия диапазона от меньшего с заполненными данными", func(t *testing.T) {
assert.Equal(t,
[]bson.D{
{{Key: "test", Value: bson.M{"$lte": "2020"}}},
{{Key: "test", Value: nil}},
},
expression.GetAscRangeConditionBSON("test", "2020"),
)
assert.Equal(t,
[]bson.D{
{{Key: "test2", Value: bson.M{"$lte": 200.00}}},
{{Key: "test2", Value: nil}},
},
expression.GetAscRangeConditionBSON("test2", 200.00),
)
})
}
func TestGetValueConditionBSON(t *testing.T) {
t.Run("Получение bson условия значения с заполненными данными", func(t *testing.T) {
assert.Equal(t,
[]bson.D{
{{Key: "test", Value: "2020"}},
{{Key: "test", Value: nil}},
},
expression.GetValueConditionBSON("test", "2020"),
)
assert.Equal(t,
[]bson.D{
{{Key: "test2", Value: 200.00}},
{{Key: "test2", Value: nil}},
},
expression.GetValueConditionBSON("test2", 200.00),
)
})
}
func TestGetPerionConditionBSON(t *testing.T) {
t.Run("Получение bson условия периода с заполненными данными", func(t *testing.T) {
assert.Equal(t,
[]bson.M{
{
"$and": []bson.D{
{{Key: fields.PeriodCondition.From, Value: bson.M{"$lte": primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC())}}},
{{Key: fields.PeriodCondition.To, Value: bson.M{"$gte": primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC())}}},
},
},
{
"$and": []bson.D{
{{Key: fields.PeriodCondition.From, Value: nil}},
{{Key: fields.PeriodCondition.To, Value: nil}},
},
},
},
expression.GetPerionConditionBSON(
models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
expression.PeriodFieldPaths{
From: fields.PeriodCondition.From,
To: fields.PeriodCondition.To,
},
),
)
})
}

@ -1,47 +1,46 @@
package discount
import (
"go.mongodb.org/mongo-driver/bson"
"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/utils/expression"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/defaults"
)
func ConstituteCommonConditions(conditions *core.CommonDiscountCondition) []bson.M {
if conditions == nil {
return []bson.M{}
}
return []bson.M{
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.User, conditions.User)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.UserType, conditions.UserType)},
{
"$or": expression.GetPerionConditionBSON(
conditions.Period,
expression.PeriodFieldPaths{
From: fields.PeriodCondition.From,
To: fields.PeriodCondition.To,
},
),
},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PurchasesAmount, conditions.PurchasesAmount)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.CartPurchasesAmount, conditions.CartPurchasesAmount)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Coupon, defaults.GetDefaultValue(conditions.Coupon, ""))},
}
}
func ConstituteOptionalConditions(conditions *core.OptionalDiscounCondition) []bson.M {
if conditions == nil {
return []bson.M{}
}
return []bson.M{
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Product, defaults.GetDefaultValue(conditions.Product, ""))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Term, defaults.GetDefaultValue(conditions.Term, uint64(0)))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Usage, defaults.GetDefaultValue(conditions.Usage, uint64(0)))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PriceFrom, defaults.GetDefaultValue(conditions.PriceFrom, float64(0)))},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Group, defaults.GetDefaultValue(conditions.Group, ""))},
}
}
package discount
import (
"go.mongodb.org/mongo-driver/bson"
"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/utils/expression"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/defaults"
)
func ConstituteCommonConditions(conditions *core.CommonDiscountCondition) []bson.M {
if conditions == nil {
return []bson.M{}
}
return []bson.M{
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.User, conditions.User)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.UserType, conditions.UserType)},
{
"$or": expression.GetPerionConditionBSON(
conditions.Period,
expression.PeriodFieldPaths{
From: fields.PeriodCondition.From,
To: fields.PeriodCondition.To,
},
),
},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PurchasesAmount, conditions.PurchasesAmount)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.CartPurchasesAmount, conditions.CartPurchasesAmount)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Coupon, defaults.GetDefaultValue(conditions.Coupon, ""))},
}
}
func ConstituteOptionalConditions(conditions *core.OptionalDiscounCondition) []bson.M {
if conditions == nil {
return []bson.M{}
}
return []bson.M{
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Product, defaults.GetDefaultValue(conditions.Product, ""))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Term, defaults.GetDefaultValue(conditions.Term, uint64(0)))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Usage, defaults.GetDefaultValue(conditions.Usage, uint64(0)))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PriceFrom, defaults.GetDefaultValue(conditions.PriceFrom, uint64(0)))},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Group, defaults.GetDefaultValue(conditions.Group, ""))},
}
}

@ -1,140 +1,139 @@
package discount_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"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"
)
func TestConstituteCommonConditions(t *testing.T) {
userID := "user1"
userType := "nkvo"
coupon := "coupon"
purchasesAmount := float64(10000)
cartPurchasesAmount := float64(20000)
t.Run("Формирование фильтра общих условий поиска с nil", func(t *testing.T) {
assert.Equal(t, []bson.M{}, discount.ConstituteCommonConditions(nil))
})
t.Run("Формирование фильтра общих условий поиска с купоном", func(t *testing.T) {
assert.Equal(t,
[]bson.M{
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.User, userID)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.UserType, userType)},
{
"$or": expression.GetPerionConditionBSON(
models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
expression.PeriodFieldPaths{
From: fields.PeriodCondition.From,
To: fields.PeriodCondition.To,
},
),
},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PurchasesAmount, purchasesAmount)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.CartPurchasesAmount, cartPurchasesAmount)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Coupon, coupon)},
},
discount.ConstituteCommonConditions(&core.CommonDiscountCondition{
Period: models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
User: userID,
UserType: userType,
Coupon: &coupon,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
}),
)
})
t.Run("Формирование фильтра общих условий поиска без купона", func(t *testing.T) {
assert.Equal(t,
[]bson.M{
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.User, userID)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.UserType, userType)},
{
"$or": expression.GetPerionConditionBSON(
models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
expression.PeriodFieldPaths{
From: fields.PeriodCondition.From,
To: fields.PeriodCondition.To,
},
),
},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PurchasesAmount, purchasesAmount)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.CartPurchasesAmount, cartPurchasesAmount)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Coupon, "")},
},
discount.ConstituteCommonConditions(&core.CommonDiscountCondition{
Period: models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
User: userID,
UserType: userType,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
}),
)
})
}
func TestConstituteOptionalConditions(t *testing.T) {
productID := "product1"
term := uint64(14)
usage := uint64(10)
priceFrom := float64(13000)
group := "group"
t.Run("Формирование фильтра опциональных условий поиска и nil", func(t *testing.T) {
assert.Equal(t, []bson.M{}, discount.ConstituteOptionalConditions(nil))
})
t.Run("Формирование фильтра опциональных условий поиска с заполненными параметрами", func(t *testing.T) {
assert.Equal(t,
[]bson.M{
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Product, productID)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Term, term)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Usage, usage)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PriceFrom, priceFrom)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Group, group)},
},
discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{
Product: &productID,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
}),
)
})
t.Run("Формирование фильтра опциональных условий поиска с пустыми параметрами", func(t *testing.T) {
assert.Equal(t,
[]bson.M{
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Product, "")},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Term, uint64(0))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Usage, uint64(0))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PriceFrom, float64(0))},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Group, "")},
},
discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{}),
)
})
}
package discount_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"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"
)
func TestConstituteCommonConditions(t *testing.T) {
userID := "user12"
userType := "nkvo9"
coupon := "coupon11"
purchasesAmount := uint64(10000)
cartPurchasesAmount := uint64(20000)
t.Run("Формирование фильтра общих условий поиска с nil", func(t *testing.T) {
assert.Equal(t, []bson.M{}, discount.ConstituteCommonConditions(nil))
})
t.Run("Формирование фильтра общих условий поиска с купоном", func(t *testing.T) {
assert.Equal(t,
[]bson.M{
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.User, userID)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.UserType, userType)},
{
"$or": expression.GetPerionConditionBSON(
models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
expression.PeriodFieldPaths{
From: fields.PeriodCondition.From,
To: fields.PeriodCondition.To,
},
),
},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PurchasesAmount, purchasesAmount)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.CartPurchasesAmount, cartPurchasesAmount)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Coupon, coupon)},
},
discount.ConstituteCommonConditions(&core.CommonDiscountCondition{
Period: models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
User: userID,
UserType: userType,
Coupon: &coupon,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
}),
)
})
t.Run("Формирование фильтра общих условий поиска без купона", func(t *testing.T) {
assert.Equal(t,
[]bson.M{
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.User, userID)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.UserType, userType)},
{
"$or": expression.GetPerionConditionBSON(
models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
expression.PeriodFieldPaths{
From: fields.PeriodCondition.From,
To: fields.PeriodCondition.To,
},
),
},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PurchasesAmount, purchasesAmount)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.CartPurchasesAmount, cartPurchasesAmount)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Coupon, "")},
},
discount.ConstituteCommonConditions(&core.CommonDiscountCondition{
Period: models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
User: userID,
UserType: userType,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
}),
)
})
}
func TestConstituteOptionalConditions(t *testing.T) {
productID := "productgg1"
term := uint64(14)
usage := uint64(10)
priceFrom := uint64(13000)
group := "group888"
t.Run("Формирование фильтра опциональных условий поиска и nil", func(t *testing.T) {
assert.Equal(t, []bson.M{}, discount.ConstituteOptionalConditions(nil))
})
t.Run("Формирование фильтра опциональных условий поиска с заполненными параметрами", func(t *testing.T) {
assert.Equal(t,
[]bson.M{
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Product, productID)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Term, term)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Usage, usage)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PriceFrom, priceFrom)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Group, group)},
},
discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{
Product: &productID,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
}),
)
})
t.Run("Формирование фильтра опциональных условий поиска с пустыми параметрами", func(t *testing.T) {
assert.Equal(t,
[]bson.M{
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Product, "")},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Term, uint64(0))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Usage, uint64(0))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PriceFrom, uint64(0))},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Group, "")},
},
discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{}),
)
})
}

@ -1,34 +1,34 @@
package discount
import (
"go.mongodb.org/mongo-driver/bson"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
)
func ConditionFilter(conditions *core.DiscountConditions) *bson.M {
if conditions == nil {
return nil
}
expressions := make([]bson.M, 0)
commonExpressions := ConstituteCommonConditions(&conditions.Common)
if len(conditions.Optionals) == 0 {
optionalExpressions := ConstituteOptionalConditions(&core.OptionalDiscounCondition{})
expressions = append(expressions, bson.M{
"$and": append([]bson.M{}, append(commonExpressions, optionalExpressions...)...),
})
}
for _, expression := range conditions.Optionals {
optionalExpressions := ConstituteOptionalConditions(&expression)
expressions = append(expressions, bson.M{
"$and": append([]bson.M{}, append(commonExpressions, optionalExpressions...)...),
})
}
return &bson.M{
"$or": expressions,
}
}
package discount
import (
"go.mongodb.org/mongo-driver/bson"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
)
func ConditionFilter(conditions *core.DiscountConditions) *bson.M {
if conditions == nil {
return nil
}
expressions := make([]bson.M, 0)
commonExpressions := ConstituteCommonConditions(&conditions.Common)
if len(conditions.Optionals) == 0 {
optionalExpressions := ConstituteOptionalConditions(&core.OptionalDiscounCondition{})
expressions = append(expressions, bson.M{
"$and": append([]bson.M{}, append(commonExpressions, optionalExpressions...)...),
})
}
for _, expression := range conditions.Optionals {
expressionCopy := expression
optionalExpressions := ConstituteOptionalConditions(&expressionCopy)
expressions = append(expressions, bson.M{
"$and": append([]bson.M{}, append(commonExpressions, optionalExpressions...)...),
})
}
return &bson.M{
"$or": expressions,
}
}

@ -1,123 +1,122 @@
package discount_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression/discount"
)
func TestConditionFilter(t *testing.T) {
productID := "product1"
productID2 := "product2"
term := uint64(14)
usage := uint64(10)
priceFrom := float64(13000)
group := "group"
userID := "user1"
userType := "nkvo"
coupon := "coupon"
purchasesAmount := float64(10000)
cartPurchasesAmount := float64(20000)
t.Run("Формирование фильтра условий поиска с пустыми условиями", func(t *testing.T) {
assert.NotNil(t, discount.ConditionFilter(&core.DiscountConditions{}))
})
t.Run("Формирование фильтра условий поиска с nil", func(t *testing.T) {
assert.Nil(t, discount.ConditionFilter(nil))
})
t.Run("Формирование фильтра условий поиска со всеми условиями", func(t *testing.T) {
commonConditions := discount.ConstituteCommonConditions(&core.CommonDiscountCondition{
Period: models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
User: userID,
UserType: userType,
Coupon: &coupon,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
})
productCondition1 := discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{
Product: &productID,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
})
productCondition2 := discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{
Product: &productID2,
PriceFrom: &priceFrom,
})
assert.Equal(t,
&bson.M{
"$or": []bson.M{
{"$and": append([]bson.M{}, append(commonConditions, productCondition1...)...)},
{"$and": append([]bson.M{}, append(commonConditions, productCondition2...)...)},
},
},
discount.ConditionFilter(&core.DiscountConditions{
Common: core.CommonDiscountCondition{
Period: models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
User: userID,
UserType: userType,
Coupon: &coupon,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
},
Optionals: []core.OptionalDiscounCondition{
{
Product: &productID,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
},
{
Product: &productID2,
PriceFrom: &priceFrom,
},
},
}),
)
})
t.Run("Формирование фильтра условий поиска с периодом", func(t *testing.T) {
productCondition := discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{})
commonConditions := discount.ConstituteCommonConditions(&core.CommonDiscountCondition{
Period: models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
})
assert.Equal(t,
&bson.M{
"$or": []bson.M{
{"$and": append([]bson.M{}, append(commonConditions, productCondition...)...)},
},
},
discount.ConditionFilter(&core.DiscountConditions{
Common: core.CommonDiscountCondition{
Period: models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
},
}),
)
})
}
package discount_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression/discount"
)
func TestConditionFilter(t *testing.T) {
productID := "product1"
productID2 := "product2"
term := uint64(14)
usage := uint64(10)
priceFrom := uint64(13000)
group := "groupopo"
userID := "user1"
userType := "nkvo01001"
coupon := "coupon"
purchasesAmount := uint64(10000)
cartPurchasesAmount := uint64(20000)
t.Run("Формирование фильтра условий поиска с пустыми условиями", func(t *testing.T) {
assert.NotNil(t, discount.ConditionFilter(&core.DiscountConditions{}))
})
t.Run("Формирование фильтра условий поиска с nil", func(t *testing.T) {
assert.Nil(t, discount.ConditionFilter(nil))
})
t.Run("Формирование фильтра условий поиска со всеми условиями", func(t *testing.T) {
commonConditions := discount.ConstituteCommonConditions(&core.CommonDiscountCondition{
Period: models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
User: userID,
UserType: userType,
Coupon: &coupon,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
})
productCondition1 := discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{
Product: &productID,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
})
productCondition2 := discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{
Product: &productID2,
PriceFrom: &priceFrom,
})
assert.Equal(t,
&bson.M{
"$or": []bson.M{
{"$and": append([]bson.M{}, append(commonConditions, productCondition1...)...)},
{"$and": append([]bson.M{}, append(commonConditions, productCondition2...)...)},
},
},
discount.ConditionFilter(&core.DiscountConditions{
Common: core.CommonDiscountCondition{
Period: models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
User: userID,
UserType: userType,
Coupon: &coupon,
PurchasesAmount: purchasesAmount,
CartPurchasesAmount: cartPurchasesAmount,
},
Optionals: []core.OptionalDiscounCondition{
{
Product: &productID,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
},
{
Product: &productID2,
PriceFrom: &priceFrom,
},
},
}),
)
})
t.Run("Формирование фильтра условий поиска с периодом", func(t *testing.T) {
productCondition := discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{})
commonConditions := discount.ConstituteCommonConditions(&core.CommonDiscountCondition{
Period: models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
})
assert.Equal(t,
&bson.M{
"$or": []bson.M{
{"$and": append([]bson.M{}, append(commonConditions, productCondition...)...)},
},
},
discount.ConditionFilter(&core.DiscountConditions{
Common: core.CommonDiscountCondition{
Period: models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
},
}),
)
})
}

@ -1,145 +1,144 @@
package discount
import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"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/pkg/utils"
)
func GetDiscountExpression(settings *core.FilterDiscountSettings) *bson.M {
if settings == nil || utils.GetFilledFieldsCount(settings) < 2 {
return nil
}
expression := bson.M{}
commonExpression := getCommonDiscountExpression(settings)
conditionExpression := getDiscountConditionExpression(settings)
targetExpression := getDiscountTargetExpression(settings)
for key, value := range commonExpression {
expression[key] = value
}
for key, value := range conditionExpression {
expression[key] = value
}
for key, value := range targetExpression {
expression[key] = value
}
return &expression
}
func getCommonDiscountExpression(deps *core.FilterDiscountSettings) bson.M {
expression := bson.M{}
if deps.ID != nil {
expression[fields.Discount.ID] = *deps.ID
}
if deps.Name != nil {
expression[fields.Discount.Name] = *deps.Name
}
if deps.Description != nil {
expression[fields.Discount.Description] = *deps.Description
}
if deps.Layer != nil {
expression[fields.Discount.Layer] = *deps.Layer
}
if deps.Deprecated != nil {
expression[fields.Discount.Deprecated] = *deps.Deprecated
}
return expression
}
func getDiscountConditionExpression(deps *core.FilterDiscountSettings) bson.M {
expression := bson.M{}
if deps.Condition == nil {
return expression
}
if deps.Condition.Period != nil {
from := primitive.NewDateTimeFromTime(deps.Condition.Period.From)
to := primitive.NewDateTimeFromTime(deps.Condition.Period.To)
expression[fields.PeriodCondition.From] = from
expression[fields.PeriodCondition.To] = to
}
if deps.Condition.Product != nil {
expression[fields.DiscountCondition.Product] = *deps.Condition.Product
}
if deps.Condition.PriceFrom != nil {
expression[fields.DiscountCondition.PriceFrom] = *deps.Condition.PriceFrom
}
if deps.Condition.Group != nil {
expression[fields.DiscountCondition.Group] = *deps.Condition.Group
}
if deps.Condition.User != nil {
expression[fields.DiscountCondition.User] = *deps.Condition.User
}
if deps.Condition.UserType != nil {
expression[fields.DiscountCondition.UserType] = *deps.Condition.UserType
}
if deps.Condition.Coupon != nil {
expression[fields.DiscountCondition.Coupon] = *deps.Condition.Coupon
}
if deps.Condition.PurchasesAmount != nil {
expression[fields.DiscountCondition.PurchasesAmount] = *deps.Condition.PurchasesAmount
}
if deps.Condition.CartPurchasesAmount != nil {
expression[fields.DiscountCondition.CartPurchasesAmount] = *deps.Condition.CartPurchasesAmount
}
if deps.Condition.Term != nil {
expression[fields.DiscountCondition.Term] = *deps.Condition.Term
}
if deps.Condition.Usage != nil {
expression[fields.DiscountCondition.Usage] = *deps.Condition.Usage
}
return expression
}
func getDiscountTargetExpression(deps *core.FilterDiscountSettings) bson.M {
expression := bson.M{}
if deps.Products != nil {
expression[fields.DiscountTarget.Products] = *deps.Products
}
if deps.Factor != nil {
expression[fields.DiscountTarget.Factor] = *deps.Factor
}
if deps.TargetScope != nil {
expression[fields.DiscountTarget.TargetScope] = *deps.TargetScope
}
if deps.TargetGroup != nil {
expression[fields.DiscountTarget.TargetGroup] = *deps.TargetGroup
}
if deps.Overhelm != nil {
expression[fields.DiscountTarget.Overhelm] = *deps.Overhelm
}
return expression
}
package discount
import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"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/pkg/utils"
)
func GetDiscountExpression(settings *core.FilterDiscountSettings) *bson.M {
if settings == nil || utils.GetFilledFieldsCount(settings) < 2 {
return nil
}
expression := bson.M{}
commonExpression := getCommonDiscountExpression(settings)
conditionExpression := getDiscountConditionExpression(settings)
targetExpression := getDiscountTargetExpression(settings)
for key, value := range commonExpression {
expression[key] = value
}
for key, value := range conditionExpression {
expression[key] = value
}
for key, value := range targetExpression {
expression[key] = value
}
return &expression
}
func getCommonDiscountExpression(deps *core.FilterDiscountSettings) bson.M {
expression := bson.M{}
if deps.ID != nil {
expression[fields.Discount.ID] = *deps.ID
}
if deps.Name != nil {
expression[fields.Discount.Name] = *deps.Name
}
if deps.Description != nil {
expression[fields.Discount.Description] = *deps.Description
}
if deps.Layer != nil {
expression[fields.Discount.Layer] = *deps.Layer
}
if deps.Deprecated != nil {
expression[fields.Discount.Deprecated] = *deps.Deprecated
}
return expression
}
func getDiscountConditionExpression(deps *core.FilterDiscountSettings) bson.M {
expression := bson.M{}
if deps.Condition == nil {
return expression
}
if deps.Condition.Period != nil {
from := primitive.NewDateTimeFromTime(deps.Condition.Period.From)
to := primitive.NewDateTimeFromTime(deps.Condition.Period.To)
expression[fields.PeriodCondition.From] = from
expression[fields.PeriodCondition.To] = to
}
if deps.Condition.Product != nil {
expression[fields.DiscountCondition.Product] = *deps.Condition.Product
}
if deps.Condition.PriceFrom != nil {
expression[fields.DiscountCondition.PriceFrom] = *deps.Condition.PriceFrom
}
if deps.Condition.Group != nil {
expression[fields.DiscountCondition.Group] = *deps.Condition.Group
}
if deps.Condition.User != nil {
expression[fields.DiscountCondition.User] = *deps.Condition.User
}
if deps.Condition.UserType != nil {
expression[fields.DiscountCondition.UserType] = *deps.Condition.UserType
}
if deps.Condition.Coupon != nil {
expression[fields.DiscountCondition.Coupon] = *deps.Condition.Coupon
}
if deps.Condition.PurchasesAmount != nil {
expression[fields.DiscountCondition.PurchasesAmount] = *deps.Condition.PurchasesAmount
}
if deps.Condition.CartPurchasesAmount != nil {
expression[fields.DiscountCondition.CartPurchasesAmount] = *deps.Condition.CartPurchasesAmount
}
if deps.Condition.Term != nil {
expression[fields.DiscountCondition.Term] = *deps.Condition.Term
}
if deps.Condition.Usage != nil {
expression[fields.DiscountCondition.Usage] = *deps.Condition.Usage
}
return expression
}
func getDiscountTargetExpression(deps *core.FilterDiscountSettings) bson.M {
expression := bson.M{}
if deps.Products != nil {
expression[fields.DiscountTarget.Products] = *deps.Products
}
if deps.Factor != nil {
expression[fields.DiscountTarget.Factor] = *deps.Factor
}
if deps.TargetScope != nil {
expression[fields.DiscountTarget.TargetScope] = *deps.TargetScope
}
if deps.TargetGroup != nil {
expression[fields.DiscountTarget.TargetGroup] = *deps.TargetGroup
}
if deps.Overhelm != nil {
expression[fields.DiscountTarget.Overhelm] = *deps.Overhelm
}
return expression
}

@ -1,122 +1,121 @@
package discount_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"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/discount"
)
func TestGetDiscountExpression(t *testing.T) {
id := "id1"
name := "discount"
description := ""
layer := uint32(3)
deprecated := false
user := "testUser"
userType := "nkvo"
coupon := "coupon1"
product := "p1"
priceFrom := float64(200)
cartPurchasesAmount := float64(20000)
purchasesAmount := float64(10000)
usage := uint64(20)
term := uint64(40)
factor := 0.95
targetScope := models.TargetSum
targetGroup := "group"
overhelm := true
condition := models.DiscountCondition{
User: &user,
Product: &product,
PriceFrom: &priceFrom,
Group: &targetGroup,
Usage: &usage,
UserType: &userType,
Coupon: &coupon,
CartPurchasesAmount: &cartPurchasesAmount,
PurchasesAmount: &purchasesAmount,
Term: &term,
Period: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
}
products := []models.ProductTarget{
{
ID: "p1",
Factor: 0.42,
Overhelm: false,
},
}
t.Run("Формирование запроса фильтрации с пустыми параметрами", func(t *testing.T) {
assert.Nil(t, discount.GetDiscountExpression(&core.FilterDiscountSettings{}))
})
t.Run("Формирование запроса фильтрации с nil", func(t *testing.T) {
assert.Nil(t, discount.GetDiscountExpression(nil))
})
t.Run("Формирование запроса фильтрации с заполненными параметрами", func(t *testing.T) {
assert.Equal(t,
&bson.M{
fields.Discount.ID: id,
fields.Discount.Name: name,
fields.Discount.Description: description,
fields.Discount.Layer: layer,
fields.Discount.Deprecated: deprecated,
fields.PeriodCondition.From: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()),
fields.PeriodCondition.To: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()),
fields.DiscountCondition.Product: product,
fields.DiscountCondition.PriceFrom: priceFrom,
fields.DiscountCondition.PurchasesAmount: purchasesAmount,
fields.DiscountCondition.CartPurchasesAmount: cartPurchasesAmount,
fields.DiscountCondition.Group: targetGroup,
fields.DiscountCondition.Usage: usage,
fields.DiscountCondition.User: user,
fields.DiscountCondition.UserType: userType,
fields.DiscountCondition.Coupon: coupon,
fields.DiscountCondition.Term: term,
fields.DiscountTarget.TargetGroup: targetGroup,
fields.DiscountTarget.TargetScope: targetScope,
fields.DiscountTarget.Factor: factor,
fields.DiscountTarget.Overhelm: overhelm,
fields.DiscountTarget.Products: products,
},
discount.GetDiscountExpression(&core.FilterDiscountSettings{
ID: &id,
Name: &name,
Description: &description,
Layer: &layer,
Deprecated: &deprecated,
Factor: &factor,
TargetScope: &targetScope,
TargetGroup: &targetGroup,
Overhelm: &overhelm,
Condition: &condition,
Products: &products,
}),
)
})
t.Run("Формирование запроса фильтрации с отсутствующими параметрами", func(t *testing.T) {
assert.Equal(t,
&bson.M{
fields.Discount.Name: name,
fields.Discount.Description: description,
},
discount.GetDiscountExpression(&core.FilterDiscountSettings{
Name: &name,
Description: &description,
}),
)
})
}
package discount_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"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/discount"
)
func TestGetDiscountExpression(t *testing.T) {
id := "id1"
name := "discount1oo"
description := ""
layer := uint32(3)
deprecated := false
user := "testUser3"
userType := "nkvo090909"
coupon := "coupon14"
product := "p12"
priceFrom := uint64(200)
cartPurchasesAmount := uint64(20000)
purchasesAmount := uint64(10000)
usage := uint64(20)
term := uint64(40)
factor := 0.95
targetScope := models.TargetSum
targetGroup := "groupffffff"
overhelm := true
condition := models.DiscountCondition{
User: &user,
Product: &product,
PriceFrom: &priceFrom,
Group: &targetGroup,
Usage: &usage,
UserType: &userType,
Coupon: &coupon,
CartPurchasesAmount: &cartPurchasesAmount,
PurchasesAmount: &purchasesAmount,
Term: &term,
Period: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
}
products := []models.ProductTarget{
{
ID: "p1",
Factor: 0.42,
Overhelm: false,
},
}
t.Run("Формирование запроса фильтрации с пустыми параметрами", func(t *testing.T) {
assert.Nil(t, discount.GetDiscountExpression(&core.FilterDiscountSettings{}))
})
t.Run("Формирование запроса фильтрации с nil", func(t *testing.T) {
assert.Nil(t, discount.GetDiscountExpression(nil))
})
t.Run("Формирование запроса фильтрации с заполненными параметрами", func(t *testing.T) {
assert.Equal(t,
&bson.M{
fields.Discount.ID: id,
fields.Discount.Name: name,
fields.Discount.Description: description,
fields.Discount.Layer: layer,
fields.Discount.Deprecated: deprecated,
fields.PeriodCondition.From: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()),
fields.PeriodCondition.To: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()),
fields.DiscountCondition.Product: product,
fields.DiscountCondition.PriceFrom: priceFrom,
fields.DiscountCondition.PurchasesAmount: purchasesAmount,
fields.DiscountCondition.CartPurchasesAmount: cartPurchasesAmount,
fields.DiscountCondition.Group: targetGroup,
fields.DiscountCondition.Usage: usage,
fields.DiscountCondition.User: user,
fields.DiscountCondition.UserType: userType,
fields.DiscountCondition.Coupon: coupon,
fields.DiscountCondition.Term: term,
fields.DiscountTarget.TargetGroup: targetGroup,
fields.DiscountTarget.TargetScope: targetScope,
fields.DiscountTarget.Factor: factor,
fields.DiscountTarget.Overhelm: overhelm,
fields.DiscountTarget.Products: products,
},
discount.GetDiscountExpression(&core.FilterDiscountSettings{
ID: &id,
Name: &name,
Description: &description,
Layer: &layer,
Deprecated: &deprecated,
Factor: &factor,
TargetScope: &targetScope,
TargetGroup: &targetGroup,
Overhelm: &overhelm,
Condition: &condition,
Products: &products,
}),
)
})
t.Run("Формирование запроса фильтрации с отсутствующими параметрами", func(t *testing.T) {
assert.Equal(t,
&bson.M{
fields.Discount.Name: name,
fields.Discount.Description: description,
},
discount.GetDiscountExpression(&core.FilterDiscountSettings{
Name: &name,
Description: &description,
}),
)
})
}

@ -1,72 +1,71 @@
package discount
import (
"go.mongodb.org/mongo-driver/bson"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert"
)
type UpdateDiscountExpression struct {
ID string
Expression *bson.M
}
func GetUpdateDiscountsExpressions(deps []core.UpdateDiscountSettings) []UpdateDiscountExpression {
discounts := make([]UpdateDiscountExpression, 0, len(deps))
requestsMap := convert.ArrayToMap(deps, func(element core.UpdateDiscountSettings) string {
return element.ID
})
for _, request := range requestsMap {
updateExpression := GetUpdateDiscountExpression(&core.UpdateDiscountSettings{
ID: request.ID,
Name: request.Name,
Description: request.Description,
Layer: request.Layer,
Deprecated: request.Deprecated,
Factor: request.Factor,
TargetScope: request.TargetScope,
TargetGroup: request.TargetGroup,
Overhelm: request.Overhelm,
Condition: request.Condition,
Products: request.Products,
})
if updateExpression == nil {
continue
}
discounts = append(discounts, *updateExpression)
}
return discounts
}
func GetUpdateDiscountExpression(deps *core.UpdateDiscountSettings) *UpdateDiscountExpression {
if deps == nil {
return nil
}
updateExpression := GetDiscountExpression(&core.FilterDiscountSettings{
Condition: deps.Condition,
Name: deps.Name,
Description: deps.Description,
Layer: deps.Layer,
Deprecated: deps.Deprecated,
Products: deps.Products,
Factor: deps.Factor,
TargetScope: deps.TargetScope,
TargetGroup: deps.TargetGroup,
Overhelm: deps.Overhelm,
})
if updateExpression == nil {
return nil
}
return &UpdateDiscountExpression{
ID: deps.ID,
Expression: &bson.M{"$set": *updateExpression},
}
}
package discount
import (
"go.mongodb.org/mongo-driver/bson"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert"
)
type UpdateDiscountExpression struct {
ID string
Expression *bson.M
}
func GetUpdateDiscountsExpressions(deps []core.UpdateDiscountSettings) []UpdateDiscountExpression {
discounts := make([]UpdateDiscountExpression, 0, len(deps))
requestsMap := convert.ArrayToMap(deps, func(element core.UpdateDiscountSettings) string {
return element.ID
})
for _, request := range requestsMap {
updateExpression := GetUpdateDiscountExpression(&core.UpdateDiscountSettings{
ID: request.ID,
Name: request.Name,
Description: request.Description,
Layer: request.Layer,
Deprecated: request.Deprecated,
Factor: request.Factor,
TargetScope: request.TargetScope,
TargetGroup: request.TargetGroup,
Overhelm: request.Overhelm,
Condition: request.Condition,
Products: request.Products,
})
if updateExpression == nil {
continue
}
discounts = append(discounts, *updateExpression)
}
return discounts
}
func GetUpdateDiscountExpression(deps *core.UpdateDiscountSettings) *UpdateDiscountExpression {
if deps == nil {
return nil
}
updateExpression := GetDiscountExpression(&core.FilterDiscountSettings{
Condition: deps.Condition,
Name: deps.Name,
Description: deps.Description,
Layer: deps.Layer,
Deprecated: deps.Deprecated,
Products: deps.Products,
Factor: deps.Factor,
TargetScope: deps.TargetScope,
TargetGroup: deps.TargetGroup,
Overhelm: deps.Overhelm,
})
if updateExpression == nil {
return nil
}
return &UpdateDiscountExpression{
ID: deps.ID,
Expression: &bson.M{"$set": *updateExpression},
}
}

@ -1,185 +1,184 @@
package discount_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"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/discount"
)
func TestGetUpdateDiscountExpression(t *testing.T) {
name := "discount"
description := ""
layer := uint32(3)
deprecated := false
user := "testUser"
userType := "nkvo"
coupon := "coupon1"
product := "p1"
priceFrom := float64(200)
cartPurchasesAmount := float64(20000)
purchasesAmount := float64(10000)
usage := uint64(20)
term := uint64(40)
factor := 0.95
targetScope := models.TargetSum
targetGroup := "group"
overhelm := true
condition := models.DiscountCondition{
User: &user,
Product: &product,
PriceFrom: &priceFrom,
Group: &targetGroup,
Usage: &usage,
UserType: &userType,
Coupon: &coupon,
CartPurchasesAmount: &cartPurchasesAmount,
PurchasesAmount: &purchasesAmount,
Term: &term,
Period: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
}
products := []models.ProductTarget{
{
ID: "p1",
Factor: 0.42,
Overhelm: false,
},
}
t.Run("Формирование запроса обновления с пустыми параметрами", func(t *testing.T) {
assert.Nil(t, discount.GetUpdateDiscountExpression(&core.UpdateDiscountSettings{}))
})
t.Run("Формирование запроса обновления с nil", func(t *testing.T) {
assert.Nil(t, discount.GetUpdateDiscountExpression(nil))
})
t.Run("Формирование запроса обновления с заполненными параметрами", func(t *testing.T) {
assert.Equal(t,
&discount.UpdateDiscountExpression{
ID: "test",
Expression: &bson.M{
"$set": bson.M{
fields.Discount.Name: name,
fields.Discount.Description: description,
fields.Discount.Layer: layer,
fields.Discount.Deprecated: deprecated,
fields.PeriodCondition.From: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()),
fields.PeriodCondition.To: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()),
fields.DiscountCondition.Product: product,
fields.DiscountCondition.PriceFrom: priceFrom,
fields.DiscountCondition.PurchasesAmount: purchasesAmount,
fields.DiscountCondition.CartPurchasesAmount: cartPurchasesAmount,
fields.DiscountCondition.Group: targetGroup,
fields.DiscountCondition.Usage: usage,
fields.DiscountCondition.User: user,
fields.DiscountCondition.UserType: userType,
fields.DiscountCondition.Coupon: coupon,
fields.DiscountCondition.Term: term,
fields.DiscountTarget.TargetGroup: targetGroup,
fields.DiscountTarget.TargetScope: targetScope,
fields.DiscountTarget.Factor: factor,
fields.DiscountTarget.Overhelm: overhelm,
fields.DiscountTarget.Products: products,
},
},
},
discount.GetUpdateDiscountExpression(&core.UpdateDiscountSettings{
ID: "test",
Name: &name,
Description: &description,
Layer: &layer,
Deprecated: &deprecated,
Factor: &factor,
TargetScope: &targetScope,
TargetGroup: &targetGroup,
Overhelm: &overhelm,
Condition: &condition,
Products: &products,
}),
)
})
t.Run("Формирование запроса обновления с отсутствующими параметрами", func(t *testing.T) {
assert.Equal(t,
&discount.UpdateDiscountExpression{
Expression: &bson.M{
"$set": bson.M{
fields.Discount.Name: name,
fields.Discount.Description: description,
},
},
},
discount.GetUpdateDiscountExpression(&core.UpdateDiscountSettings{
Name: &name,
Description: &description,
}),
)
})
}
func TestGetUpdateDiscountsExpressions(t *testing.T) {
name1 := "discount1"
name2 := "discount2"
description := ""
t.Run("Формирование запросов обновления с пустым массивом", func(t *testing.T) {
assert.Equal(t,
[]discount.UpdateDiscountExpression{},
discount.GetUpdateDiscountsExpressions([]core.UpdateDiscountSettings{}),
)
})
t.Run("Формирование запроса обновления с отсутствующими и пустыми параметрами, а также с одинаковыми ID", func(t *testing.T) {
assert.ElementsMatch(t,
[]discount.UpdateDiscountExpression{
{
ID: "discount1",
Expression: &bson.M{
"$set": bson.M{
fields.Discount.Name: name1,
fields.Discount.Description: description,
},
},
},
{
ID: "discount2",
Expression: &bson.M{
"$set": bson.M{
fields.Discount.Name: name2,
fields.Discount.Description: description,
},
},
},
},
discount.GetUpdateDiscountsExpressions([]core.UpdateDiscountSettings{
{
ID: "discount1",
Name: &name2,
Description: &description,
},
{
ID: "discount1",
Name: &name1,
Description: &description,
},
{
ID: "discount2",
Name: &name2,
Description: &description,
},
{},
}),
)
})
}
package discount_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"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/discount"
)
func TestGetUpdateDiscountExpression(t *testing.T) {
name := "discount"
description := ""
layer := uint32(3)
deprecated := false
user := "testUser"
userType := "nkvo"
coupon := "coupon1"
product := "p1"
priceFrom := uint64(200)
cartPurchasesAmount := uint64(20000)
purchasesAmount := uint64(10000)
usage := uint64(20)
term := uint64(40)
factor := 0.95
targetScope := models.TargetSum
targetGroup := "group"
overhelm := true
condition := models.DiscountCondition{
User: &user,
Product: &product,
PriceFrom: &priceFrom,
Group: &targetGroup,
Usage: &usage,
UserType: &userType,
Coupon: &coupon,
CartPurchasesAmount: &cartPurchasesAmount,
PurchasesAmount: &purchasesAmount,
Term: &term,
Period: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
}
products := []models.ProductTarget{
{
ID: "p1",
Factor: 0.42,
Overhelm: false,
},
}
t.Run("Формирование запроса обновления с пустыми параметрами", func(t *testing.T) {
assert.Nil(t, discount.GetUpdateDiscountExpression(&core.UpdateDiscountSettings{}))
})
t.Run("Формирование запроса обновления с nil", func(t *testing.T) {
assert.Nil(t, discount.GetUpdateDiscountExpression(nil))
})
t.Run("Формирование запроса обновления с заполненными параметрами", func(t *testing.T) {
assert.Equal(t,
&discount.UpdateDiscountExpression{
ID: "test",
Expression: &bson.M{
"$set": bson.M{
fields.Discount.Name: name,
fields.Discount.Description: description,
fields.Discount.Layer: layer,
fields.Discount.Deprecated: deprecated,
fields.PeriodCondition.From: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()),
fields.PeriodCondition.To: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()),
fields.DiscountCondition.Product: product,
fields.DiscountCondition.PriceFrom: priceFrom,
fields.DiscountCondition.PurchasesAmount: purchasesAmount,
fields.DiscountCondition.CartPurchasesAmount: cartPurchasesAmount,
fields.DiscountCondition.Group: targetGroup,
fields.DiscountCondition.Usage: usage,
fields.DiscountCondition.User: user,
fields.DiscountCondition.UserType: userType,
fields.DiscountCondition.Coupon: coupon,
fields.DiscountCondition.Term: term,
fields.DiscountTarget.TargetGroup: targetGroup,
fields.DiscountTarget.TargetScope: targetScope,
fields.DiscountTarget.Factor: factor,
fields.DiscountTarget.Overhelm: overhelm,
fields.DiscountTarget.Products: products,
},
},
},
discount.GetUpdateDiscountExpression(&core.UpdateDiscountSettings{
ID: "test",
Name: &name,
Description: &description,
Layer: &layer,
Deprecated: &deprecated,
Factor: &factor,
TargetScope: &targetScope,
TargetGroup: &targetGroup,
Overhelm: &overhelm,
Condition: &condition,
Products: &products,
}),
)
})
t.Run("Формирование запроса обновления с отсутствующими параметрами", func(t *testing.T) {
assert.Equal(t,
&discount.UpdateDiscountExpression{
Expression: &bson.M{
"$set": bson.M{
fields.Discount.Name: name,
fields.Discount.Description: description,
},
},
},
discount.GetUpdateDiscountExpression(&core.UpdateDiscountSettings{
Name: &name,
Description: &description,
}),
)
})
}
func TestGetUpdateDiscountsExpressions(t *testing.T) {
name1 := "discount1"
name2 := "discount2"
description := ""
t.Run("Формирование запросов обновления с пустым массивом", func(t *testing.T) {
assert.Equal(t,
[]discount.UpdateDiscountExpression{},
discount.GetUpdateDiscountsExpressions([]core.UpdateDiscountSettings{}),
)
})
t.Run("Формирование запроса обновления с отсутствующими и пустыми параметрами, а также с одинаковыми ID", func(t *testing.T) {
assert.ElementsMatch(t,
[]discount.UpdateDiscountExpression{
{
ID: "discount1",
Expression: &bson.M{
"$set": bson.M{
fields.Discount.Name: name1,
fields.Discount.Description: description,
},
},
},
{
ID: "discount2",
Expression: &bson.M{
"$set": bson.M{
fields.Discount.Name: name2,
fields.Discount.Description: description,
},
},
},
},
discount.GetUpdateDiscountsExpressions([]core.UpdateDiscountSettings{
{
ID: "discount1",
Name: &name2,
Description: &description,
},
{
ID: "discount1",
Name: &name1,
Description: &description,
},
{
ID: "discount2",
Name: &name2,
Description: &description,
},
{},
}),
)
})
}

@ -1,30 +1,32 @@
package utils
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
)
func FilterCouponDiscounts(discounts []models.Discount) ([]models.Discount, *models.Discount) {
if len(discounts) == 0 {
return []models.Discount{}, nil
}
var couponDiscount *models.Discount
notCouponDiscount := make([]models.Discount, 0, len(discounts))
for _, discount := range discounts {
isCouponDiscount := discount.Condition.Coupon != nil && *discount.Condition.Coupon != ""
currentDiscount := discount
if isCouponDiscount && couponDiscount != nil {
continue
}
if isCouponDiscount && couponDiscount == nil {
couponDiscount = &currentDiscount
}
notCouponDiscount = append(notCouponDiscount, discount)
}
return notCouponDiscount, couponDiscount
}
package utils
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
)
func FilterCouponDiscounts(discounts []models.Discount) ([]models.Discount, *models.Discount) {
if len(discounts) == 0 {
return []models.Discount{}, nil
}
var couponDiscount *models.Discount
notCouponDiscount := make([]models.Discount, 0, len(discounts))
for _, discount := range discounts {
isCouponDiscount := discount.Condition.Coupon != nil && *discount.Condition.Coupon != ""
currentDiscount := discount
if isCouponDiscount && couponDiscount != nil {
continue
}
if isCouponDiscount && couponDiscount == nil {
couponDiscount = &currentDiscount
}
notCouponDiscount = append(notCouponDiscount, discount)
}
return notCouponDiscount, couponDiscount
}

@ -1,48 +1,47 @@
package utils_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils"
)
func TestFilterDiscounts(t *testing.T) {
coupon1 := "testCoupon1"
coupon2 := "testCoupon2"
user := "user1"
t.Run("Корректная фильтрация купоночных скидок", func(t *testing.T) {
filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts([]models.Discount{
{Name: "1", Condition: models.DiscountCondition{Coupon: &coupon1}},
{Name: "11", Condition: models.DiscountCondition{Coupon: &coupon2}},
{Name: "2", Condition: models.DiscountCondition{User: &user}},
{Name: "3"},
{Name: "4"},
})
assert.ElementsMatch(t, []models.Discount{
{Name: "1", Condition: models.DiscountCondition{Coupon: &coupon1}},
{Name: "2", Condition: models.DiscountCondition{User: &user}},
{Name: "3"},
{Name: "4"},
}, filteredDiscounts)
assert.Equal(t, &models.Discount{Name: "1", Condition: models.DiscountCondition{Coupon: &coupon1}}, couponDiscount)
})
t.Run("Корректная фильтрация купоночных скидок с пустым значением", func(t *testing.T) {
filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts([]models.Discount{})
assert.Equal(t, []models.Discount{}, filteredDiscounts)
assert.Nil(t, couponDiscount)
})
t.Run("Корректная фильтрация купоночных скидок без купоночной скидки", func(t *testing.T) {
filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts([]models.Discount{{}})
assert.Equal(t, []models.Discount{{}}, filteredDiscounts)
assert.Nil(t, couponDiscount)
})
}
package utils_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils"
)
func TestFilterDiscounts(t *testing.T) {
coupon1 := "testCoupon1"
coupon2 := "testCoupon2"
user := "user1"
t.Run("Корректная фильтрация купоночных скидок", func(t *testing.T) {
filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts([]models.Discount{
{Name: "1", Condition: models.DiscountCondition{Coupon: &coupon1}},
{Name: "11", Condition: models.DiscountCondition{Coupon: &coupon2}},
{Name: "2", Condition: models.DiscountCondition{User: &user}},
{Name: "3"},
{Name: "4"},
})
assert.ElementsMatch(t, []models.Discount{
{Name: "1", Condition: models.DiscountCondition{Coupon: &coupon1}},
{Name: "2", Condition: models.DiscountCondition{User: &user}},
{Name: "3"},
{Name: "4"},
}, filteredDiscounts)
assert.Equal(t, &models.Discount{Name: "1", Condition: models.DiscountCondition{Coupon: &coupon1}}, couponDiscount)
})
t.Run("Корректная фильтрация купоночных скидок с пустым значением", func(t *testing.T) {
filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts([]models.Discount{})
assert.Equal(t, []models.Discount{}, filteredDiscounts)
assert.Nil(t, couponDiscount)
})
t.Run("Корректная фильтрация купоночных скидок без купоночной скидки", func(t *testing.T) {
filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts([]models.Discount{{}})
assert.Equal(t, []models.Discount{{}}, filteredDiscounts)
assert.Nil(t, couponDiscount)
})
}

@ -1,51 +1,50 @@
package transfer
import (
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
func AuditModelToProto(model *models.Audit) *discount.Audit {
proto := discount.Audit{}
if model == nil {
return &proto
}
proto.CreatedAt = timestamppb.New(model.CreatedAt)
proto.UpdatedAt = timestamppb.New(model.UpdatedAt)
if model.DeletedAt != nil {
proto.DeletedAt = timestamppb.New(*model.DeletedAt)
proto.Deleted = true
}
return &proto
}
func AuditProtoToModel(proto *discount.Audit) *models.Audit {
model := models.Audit{}
if proto == nil {
return &model
}
if proto.GetCreatedAt() != nil {
model.CreatedAt = proto.GetCreatedAt().AsTime()
}
if proto.GetUpdatedAt() != nil {
model.UpdatedAt = proto.GetUpdatedAt().AsTime()
}
if proto.GetDeletedAt() != nil {
deletedAt := proto.GetDeletedAt().AsTime()
model.DeletedAt = &deletedAt
}
model.Deleted = proto.GetDeleted()
return &model
}
package transfer
import (
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
func AuditModelToProto(model *models.Audit) *discount.Audit {
proto := discount.Audit{}
if model == nil {
return &proto
}
proto.CreatedAt = timestamppb.New(model.CreatedAt)
proto.UpdatedAt = timestamppb.New(model.UpdatedAt)
if model.DeletedAt != nil {
proto.DeletedAt = timestamppb.New(*model.DeletedAt)
proto.Deleted = true
}
return &proto
}
func AuditProtoToModel(proto *discount.Audit) *models.Audit {
model := models.Audit{}
if proto == nil {
return &model
}
if proto.GetCreatedAt() != nil {
model.CreatedAt = proto.GetCreatedAt().AsTime()
}
if proto.GetUpdatedAt() != nil {
model.UpdatedAt = proto.GetUpdatedAt().AsTime()
}
if proto.GetDeletedAt() != nil {
deletedAt := proto.GetDeletedAt().AsTime()
model.DeletedAt = &deletedAt
}
model.Deleted = proto.GetDeleted()
return &model
}

@ -1,135 +1,134 @@
package transfer_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
func TestAuditModelToProto(t *testing.T) {
deletedAtModel := time.Unix(1674546287, 0).UTC()
deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
t.Run("Перевод аудита с пустой модели в прототип", func(t *testing.T) {
assert.Equal(t, &discount.Audit{
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
}, transfer.AuditModelToProto(&models.Audit{}))
})
t.Run("Перевод аудита с nil модели в прототип", func(t *testing.T) {
assert.Equal(t, &discount.Audit{CreatedAt: nil, UpdatedAt: nil}, transfer.AuditModelToProto(nil))
})
t.Run("Перевод аудита с модели, которая хранит дату удаления, но флаг deleted при этом false в прототип", func(t *testing.T) {
assert.Equal(t,
&discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
DeletedAt: &deletedAtProto,
Deleted: true,
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
},
transfer.AuditModelToProto(&models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
DeletedAt: &deletedAtModel,
Deleted: false,
CreatedAt: time.Unix(1674546287, 0).UTC(),
}),
)
})
t.Run("Перевод аудита с модели, которая хранит флаг deleted равный true, но при этом не имеет даты удаления в прототип", func(t *testing.T) {
assert.Equal(t,
&discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
},
transfer.AuditModelToProto(&models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
Deleted: true,
CreatedAt: time.Unix(1674546287, 0).UTC(),
}),
)
})
t.Run("Перевод аудита с модели без информации об удалении в прототип", func(t *testing.T) {
assert.Equal(t,
&discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
},
transfer.AuditModelToProto(&models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
CreatedAt: time.Unix(1674546287, 0).UTC(),
}),
)
})
t.Run("Перевод аудита с модели в прототип", func(t *testing.T) {
assert.Equal(t,
&discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
DeletedAt: &deletedAtProto,
Deleted: true,
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
},
transfer.AuditModelToProto(&models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
DeletedAt: &deletedAtModel,
Deleted: true,
CreatedAt: time.Unix(1674546287, 0).UTC(),
}),
)
})
}
func TestAuditProtoToModel(t *testing.T) {
deletedAtModel := time.Unix(1674546287, 0).UTC()
deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
t.Run("Перевод аудита с пустого прототипа в модель", func(t *testing.T) {
assert.Equal(t, &models.Audit{}, transfer.AuditProtoToModel(&discount.Audit{}))
})
t.Run("Перевод аудита с nil прототипа в модель", func(t *testing.T) {
assert.Equal(t, &models.Audit{}, transfer.AuditProtoToModel(nil))
})
t.Run("Перевод аудита с прототипа без информации об удалении в модель", func(t *testing.T) {
assert.Equal(t,
&models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
CreatedAt: time.Unix(1674546287, 0).UTC(),
Deleted: true,
},
transfer.AuditProtoToModel(&discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
Deleted: true,
}),
)
})
t.Run("Перевод аудита с прототипа в модель", func(t *testing.T) {
assert.Equal(t,
&models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
DeletedAt: &deletedAtModel,
Deleted: true,
CreatedAt: time.Unix(1674546287, 0).UTC(),
},
transfer.AuditProtoToModel(&discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
DeletedAt: &deletedAtProto,
Deleted: true,
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
}),
)
})
}
package transfer_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
func TestAuditModelToProto(t *testing.T) {
deletedAtModel := time.Unix(1674546287, 0).UTC()
deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
t.Run("Перевод аудита с пустой модели в прототип", func(t *testing.T) {
assert.Equal(t, &discount.Audit{
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
}, transfer.AuditModelToProto(&models.Audit{}))
})
t.Run("Перевод аудита с nil модели в прототип", func(t *testing.T) {
assert.Equal(t, &discount.Audit{CreatedAt: nil, UpdatedAt: nil}, transfer.AuditModelToProto(nil))
})
t.Run("Перевод аудита с модели, которая хранит дату удаления, но флаг deleted при этом false в прототип", func(t *testing.T) {
assert.Equal(t,
&discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
DeletedAt: &deletedAtProto,
Deleted: true,
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
},
transfer.AuditModelToProto(&models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
DeletedAt: &deletedAtModel,
Deleted: false,
CreatedAt: time.Unix(1674546287, 0).UTC(),
}),
)
})
t.Run("Перевод аудита с модели, которая хранит флаг deleted равный true, но при этом не имеет даты удаления в прототип", func(t *testing.T) {
assert.Equal(t,
&discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
},
transfer.AuditModelToProto(&models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
Deleted: true,
CreatedAt: time.Unix(1674546287, 0).UTC(),
}),
)
})
t.Run("Перевод аудита с модели без информации об удалении в прототип", func(t *testing.T) {
assert.Equal(t,
&discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
},
transfer.AuditModelToProto(&models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
CreatedAt: time.Unix(1674546287, 0).UTC(),
}),
)
})
t.Run("Перевод аудита с модели в прототип", func(t *testing.T) {
assert.Equal(t,
&discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
DeletedAt: &deletedAtProto,
Deleted: true,
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
},
transfer.AuditModelToProto(&models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
DeletedAt: &deletedAtModel,
Deleted: true,
CreatedAt: time.Unix(1674546287, 0).UTC(),
}),
)
})
}
func TestAuditProtoToModel(t *testing.T) {
deletedAtModel := time.Unix(1674546287, 0).UTC()
deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
t.Run("Перевод аудита с пустого прототипа в модель", func(t *testing.T) {
assert.Equal(t, &models.Audit{}, transfer.AuditProtoToModel(&discount.Audit{}))
})
t.Run("Перевод аудита с nil прототипа в модель", func(t *testing.T) {
assert.Equal(t, &models.Audit{}, transfer.AuditProtoToModel(nil))
})
t.Run("Перевод аудита с прототипа без информации об удалении в модель", func(t *testing.T) {
assert.Equal(t,
&models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
CreatedAt: time.Unix(1674546287, 0).UTC(),
Deleted: true,
},
transfer.AuditProtoToModel(&discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
Deleted: true,
}),
)
})
t.Run("Перевод аудита с прототипа в модель", func(t *testing.T) {
assert.Equal(t,
&models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
DeletedAt: &deletedAtModel,
Deleted: true,
CreatedAt: time.Unix(1674546287, 0).UTC(),
},
transfer.AuditProtoToModel(&discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
DeletedAt: &deletedAtProto,
Deleted: true,
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
}),
)
})
}

@ -1,33 +1,33 @@
package transfer
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/defaults"
)
func DiscountCalculationTargetProtoToModel(discountTarget *proto.DiscountCalculationTarget) *models.DiscountCalculationTarget {
if discountTarget == nil {
return &models.DiscountCalculationTarget{TargetScope: models.TargetSum}
}
return &models.DiscountCalculationTarget{
Products: ProductTargetsProtoToModel(discountTarget.Products),
Factor: discountTarget.Factor,
TargetScope: models.TargetScopeModelMap[defaults.GetDefaultValue(discountTarget.TargetScope, 0)],
TargetGroup: defaults.GetDefaultValue(discountTarget.TargetGroup, ""),
Overhelm: defaults.GetDefaultValue(discountTarget.Overhelm, false),
}
}
func DiscountCalculationTargetModelToProto(discountTarget models.DiscountCalculationTarget) *proto.DiscountCalculationTarget {
scope := models.TargetScopeProtoMap[discountTarget.TargetScope]
return &proto.DiscountCalculationTarget{
Products: ProductTargetsModelToProto(discountTarget.Products),
Factor: discountTarget.Factor,
TargetScope: &scope,
TargetGroup: &discountTarget.TargetGroup,
Overhelm: &discountTarget.Overhelm,
}
}
package transfer
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/defaults"
)
func DiscountCalculationTargetProtoToModel(discountTarget *proto.DiscountCalculationTarget) *models.DiscountCalculationTarget {
if discountTarget == nil {
return &models.DiscountCalculationTarget{TargetScope: models.TargetSum}
}
return &models.DiscountCalculationTarget{
Products: ProductTargetsProtoToModel(discountTarget.Products),
Factor: discountTarget.Factor,
TargetScope: models.TargetScopeModelMap[defaults.GetDefaultValue(discountTarget.TargetScope, 0)],
TargetGroup: defaults.GetDefaultValue(discountTarget.TargetGroup, ""),
Overhelm: defaults.GetDefaultValue(discountTarget.Overhelm, false),
}
}
func DiscountCalculationTargetModelToProto(discountTarget models.DiscountCalculationTarget) *proto.DiscountCalculationTarget {
scope := models.TargetScopeProtoMap[discountTarget.TargetScope]
return &proto.DiscountCalculationTarget{
Products: ProductTargetsModelToProto(discountTarget.Products),
Factor: discountTarget.Factor,
TargetScope: &scope,
TargetGroup: &discountTarget.TargetGroup,
Overhelm: &discountTarget.Overhelm,
}
}

@ -1,93 +1,93 @@
package transfer_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
func TestDiscountCalculationTargetProtoToModel(t *testing.T) {
t.Run("Перевод цели высчитывания из proto в модель", func(t *testing.T) {
assert.Equal(t,
&models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Products: []models.ProductTarget{
{ID: "1", Factor: 20},
{ID: "2", Factor: 20},
{ID: "3", Factor: 10},
},
Factor: 20,
},
transfer.DiscountCalculationTargetProtoToModel(&discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{
{ID: "1", Factor: 20},
{ID: "2", Factor: 20},
{ID: "3", Factor: 10},
},
Factor: 20,
}),
)
})
t.Run("Перевод цели высчитывания из proto в модель с пустынми данными", func(t *testing.T) {
assert.Equal(t,
&models.DiscountCalculationTarget{
Products: []models.ProductTarget{},
TargetScope: models.TargetSum,
},
transfer.DiscountCalculationTargetProtoToModel(&discount.DiscountCalculationTarget{}),
)
})
t.Run("Перевод цели высчитывания из proto в модель с nil", func(t *testing.T) {
assert.Equal(t,
&models.DiscountCalculationTarget{TargetScope: models.TargetSum},
transfer.DiscountCalculationTargetProtoToModel(nil),
)
})
}
func TestDiscountCalculationTargetModelToProto(t *testing.T) {
t.Run("Перевод цели высчитывания из модели в proto", func(t *testing.T) {
assert.Equal(t,
&discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Overhelm: new(bool),
TargetGroup: new(string),
Products: []*discount.ProductTarget{
{ID: "1", Factor: 20, Overhelm: new(bool)},
{ID: "2", Factor: 20, Overhelm: new(bool)},
{ID: "3", Factor: 10, Overhelm: new(bool)},
},
Factor: 20,
},
transfer.DiscountCalculationTargetModelToProto(models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Products: []models.ProductTarget{
{ID: "1", Factor: 20},
{ID: "2", Factor: 20},
{ID: "3", Factor: 10},
},
Factor: 20,
}),
)
})
t.Run("Перевод цели высчитывания из модели в proto с пустыми значениями", func(t *testing.T) {
assert.Equal(t,
&discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{},
Overhelm: new(bool),
TargetGroup: new(string),
},
transfer.DiscountCalculationTargetModelToProto(models.DiscountCalculationTarget{
Products: []models.ProductTarget{},
}),
)
})
}
package transfer_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
func TestDiscountCalculationTargetProtoToModel(t *testing.T) {
t.Run("Перевод цели высчитывания из proto в модель", func(t *testing.T) {
assert.Equal(t,
&models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Products: []models.ProductTarget{
{ID: "1", Factor: 20},
{ID: "2", Factor: 20},
{ID: "3", Factor: 10},
},
Factor: 20,
},
transfer.DiscountCalculationTargetProtoToModel(&discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{
{ID: "1", Factor: 20},
{ID: "2", Factor: 20},
{ID: "3", Factor: 10},
},
Factor: 20,
}),
)
})
t.Run("Перевод цели высчитывания из proto в модель с пустынми данными", func(t *testing.T) {
assert.Equal(t,
&models.DiscountCalculationTarget{
Products: []models.ProductTarget{},
TargetScope: models.TargetSum,
},
transfer.DiscountCalculationTargetProtoToModel(&discount.DiscountCalculationTarget{}),
)
})
t.Run("Перевод цели высчитывания из proto в модель с nil", func(t *testing.T) {
assert.Equal(t,
&models.DiscountCalculationTarget{TargetScope: models.TargetSum},
transfer.DiscountCalculationTargetProtoToModel(nil),
)
})
}
func TestDiscountCalculationTargetModelToProto(t *testing.T) {
t.Run("Перевод цели высчитывания из модели в proto", func(t *testing.T) {
assert.Equal(t,
&discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Overhelm: new(bool),
TargetGroup: new(string),
Products: []*discount.ProductTarget{
{ID: "1", Factor: 20, Overhelm: new(bool)},
{ID: "2", Factor: 20, Overhelm: new(bool)},
{ID: "3", Factor: 10, Overhelm: new(bool)},
},
Factor: 20,
},
transfer.DiscountCalculationTargetModelToProto(models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Products: []models.ProductTarget{
{ID: "1", Factor: 20},
{ID: "2", Factor: 20},
{ID: "3", Factor: 10},
},
Factor: 20,
}),
)
})
t.Run("Перевод цели высчитывания из модели в proto с пустыми значениями", func(t *testing.T) {
assert.Equal(t,
&discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{},
Overhelm: new(bool),
TargetGroup: new(string),
},
transfer.DiscountCalculationTargetModelToProto(models.DiscountCalculationTarget{
Products: []models.ProductTarget{},
}),
)
})
}

@ -1,46 +1,46 @@
package transfer
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
func DiscountConditionModelToProto(discountCondition *models.DiscountCondition) *proto.DiscountCondition {
if discountCondition == nil {
return nil
}
return &proto.DiscountCondition{
Period: PeriodModelToProto(discountCondition.Period),
User: discountCondition.User,
UserType: discountCondition.UserType,
Coupon: discountCondition.Coupon,
PurchasesAmount: discountCondition.PurchasesAmount,
CartPurchasesAmount: discountCondition.CartPurchasesAmount,
Term: discountCondition.Term,
Product: discountCondition.Product,
Usage: discountCondition.Usage,
PriceFrom: discountCondition.PriceFrom,
Group: discountCondition.Group,
}
}
func DiscountConditionProtoToModel(discountCondition *proto.DiscountCondition) *models.DiscountCondition {
if discountCondition == nil {
return &models.DiscountCondition{}
}
return &models.DiscountCondition{
Period: PeriodProtoToModel(discountCondition.GetPeriod()),
Product: discountCondition.Product,
User: discountCondition.User,
UserType: discountCondition.UserType,
Coupon: discountCondition.Coupon,
PurchasesAmount: discountCondition.PurchasesAmount,
CartPurchasesAmount: discountCondition.CartPurchasesAmount,
Term: discountCondition.Term,
Usage: discountCondition.Usage,
PriceFrom: discountCondition.PriceFrom,
Group: discountCondition.Group,
}
}
package transfer
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
func DiscountConditionModelToProto(discountCondition *models.DiscountCondition) *proto.DiscountCondition {
if discountCondition == nil {
return nil
}
return &proto.DiscountCondition{
Period: PeriodModelToProto(discountCondition.Period),
User: discountCondition.User,
UserType: discountCondition.UserType,
Coupon: discountCondition.Coupon,
PurchasesAmount: discountCondition.PurchasesAmount,
CartPurchasesAmount: discountCondition.CartPurchasesAmount,
Term: discountCondition.Term,
Product: discountCondition.Product,
Usage: discountCondition.Usage,
PriceFrom: discountCondition.PriceFrom,
Group: discountCondition.Group,
}
}
func DiscountConditionProtoToModel(discountCondition *proto.DiscountCondition) *models.DiscountCondition {
if discountCondition == nil {
return &models.DiscountCondition{}
}
return &models.DiscountCondition{
Period: PeriodProtoToModel(discountCondition.GetPeriod()),
Product: discountCondition.Product,
User: discountCondition.User,
UserType: discountCondition.UserType,
Coupon: discountCondition.Coupon,
PurchasesAmount: discountCondition.PurchasesAmount,
CartPurchasesAmount: discountCondition.CartPurchasesAmount,
Term: discountCondition.Term,
Usage: discountCondition.Usage,
PriceFrom: discountCondition.PriceFrom,
Group: discountCondition.Group,
}
}

@ -1,142 +1,141 @@
package transfer_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
func TestDiscountConditionProtoToModel(t *testing.T) {
userID := "1"
userType := "nkvo"
coupon := "test"
product := "product1"
purchasesAmount := float64(20000)
cartPurchasesAmount := float64(20000)
term := uint64(20)
usage := uint64(20)
priceFrom := float64(20000)
group := "testGroup"
t.Run("Перевод не до конца заполненных условий скидок с proto в модель", func(t *testing.T) {
assert.Equal(t,
&models.DiscountCondition{CartPurchasesAmount: &cartPurchasesAmount},
transfer.DiscountConditionProtoToModel(&discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
}),
)
})
t.Run("Перевод скидок с nil proto в модель", func(t *testing.T) {
assert.Equal(t, &models.DiscountCondition{}, transfer.DiscountConditionProtoToModel(nil))
})
t.Run("Перевод условий скидок с proto в модель", func(t *testing.T) {
assert.Equal(t,
&models.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
User: &userID,
UserType: &userType,
PurchasesAmount: &purchasesAmount,
Coupon: &coupon,
Product: &product,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
Period: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
},
transfer.DiscountConditionProtoToModel(&discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
User: &userID,
UserType: &userType,
PurchasesAmount: &purchasesAmount,
Coupon: &coupon,
Product: &product,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
Period: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
To: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
},
}),
)
})
}
func TestDiscountConditionModelToProto(t *testing.T) {
userID := "1"
userType := "nkvo"
coupon := "test"
product := "product1"
purchasesAmount := float64(20000)
cartPurchasesAmount := float64(20000)
term := uint64(20)
usage := uint64(20)
priceFrom := float64(20000)
group := "testGroup"
t.Run("Перевод условий скидки с модели в proto", func(t *testing.T) {
assert.Equal(t,
&discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
User: &userID,
UserType: &userType,
PurchasesAmount: &purchasesAmount,
Coupon: &coupon,
Product: &product,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
Period: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
To: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
},
},
transfer.DiscountConditionModelToProto(&models.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
User: &userID,
UserType: &userType,
PurchasesAmount: &purchasesAmount,
Coupon: &coupon,
Product: &product,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
Period: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
}),
)
})
t.Run("Перевод условий скидки с nil модели в proto", func(t *testing.T) {
assert.Nil(t, transfer.DiscountConditionModelToProto(nil))
})
}
package transfer_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
func TestDiscountConditionProtoToModel(t *testing.T) {
userID := "1"
userType := "nkvo11"
coupon := "test22"
product := "product13"
purchasesAmount := uint64(20000)
cartPurchasesAmount := uint64(20000)
term := uint64(20)
usage := uint64(20)
priceFrom := uint64(20000)
group := "testGroupo"
t.Run("Перевод не до конца заполненных условий скидок с proto в модель", func(t *testing.T) {
assert.Equal(t,
&models.DiscountCondition{CartPurchasesAmount: &cartPurchasesAmount},
transfer.DiscountConditionProtoToModel(&discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
}),
)
})
t.Run("Перевод скидок с nil proto в модель", func(t *testing.T) {
assert.Equal(t, &models.DiscountCondition{}, transfer.DiscountConditionProtoToModel(nil))
})
t.Run("Перевод условий скидок с proto в модель", func(t *testing.T) {
assert.Equal(t,
&models.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
User: &userID,
UserType: &userType,
PurchasesAmount: &purchasesAmount,
Coupon: &coupon,
Product: &product,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
Period: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
},
transfer.DiscountConditionProtoToModel(&discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
User: &userID,
UserType: &userType,
PurchasesAmount: &purchasesAmount,
Coupon: &coupon,
Product: &product,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
Period: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
To: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
},
}),
)
})
}
func TestDiscountConditionModelToProto(t *testing.T) {
userID := "1"
userType := "nkvo"
coupon := "test"
product := "product1"
purchasesAmount := uint64(20000)
cartPurchasesAmount := uint64(20000)
term := uint64(20)
usage := uint64(20)
priceFrom := uint64(20000)
group := "testGroupbgfbgbgb"
t.Run("Перевод условий скидки с модели в proto", func(t *testing.T) {
assert.Equal(t,
&discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
User: &userID,
UserType: &userType,
PurchasesAmount: &purchasesAmount,
Coupon: &coupon,
Product: &product,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
Period: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
To: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
},
},
transfer.DiscountConditionModelToProto(&models.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
User: &userID,
UserType: &userType,
PurchasesAmount: &purchasesAmount,
Coupon: &coupon,
Product: &product,
Term: &term,
Usage: &usage,
PriceFrom: &priceFrom,
Group: &group,
Period: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
}),
)
})
t.Run("Перевод условий скидки с nil модели в proto", func(t *testing.T) {
assert.Nil(t, transfer.DiscountConditionModelToProto(nil))
})
}

@ -1,60 +1,60 @@
package transfer
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
func DiscountsProtoToModel(discounts *proto.Discounts) []*models.Discount {
if discounts == nil {
return []*models.Discount{}
}
modelDiscounts := make([]*models.Discount, len(discounts.Discounts))
for index, discount := range discounts.Discounts {
modelDiscounts[index] = DiscountProtoToModel(discount)
}
return modelDiscounts
}
func DiscountProtoToModel(discount *proto.Discount) *models.Discount {
if discount == nil {
return nil
}
return &models.Discount{
ID: discount.GetID(),
Name: discount.GetName(),
Layer: discount.GetLayer(),
Description: discount.GetDescription(),
Condition: *DiscountConditionProtoToModel(discount.GetCondition()),
Target: *DiscountCalculationTargetProtoToModel(discount.GetTarget()),
Audit: *AuditProtoToModel(discount.GetAudit()),
}
}
func DiscountsModelToProto(discounts []models.Discount) *proto.Discounts {
protoDiscounts := make([]*proto.Discount, len(discounts))
for index, discount := range discounts {
protoDiscounts[index] = DiscountModelToProto(discount)
}
return &proto.Discounts{
Discounts: protoDiscounts,
}
}
func DiscountModelToProto(discount models.Discount) *proto.Discount {
return &proto.Discount{
ID: discount.ID,
Name: discount.Name,
Layer: discount.Layer,
Description: discount.Description,
Condition: DiscountConditionModelToProto(&discount.Condition),
Target: DiscountCalculationTargetModelToProto(discount.Target),
Audit: AuditModelToProto(&discount.Audit),
}
}
package transfer
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
func DiscountsProtoToModel(discounts *proto.Discounts) []*models.Discount {
if discounts == nil {
return []*models.Discount{}
}
modelDiscounts := make([]*models.Discount, len(discounts.Discounts))
for index, discount := range discounts.Discounts {
modelDiscounts[index] = DiscountProtoToModel(discount)
}
return modelDiscounts
}
func DiscountProtoToModel(discount *proto.Discount) *models.Discount {
if discount == nil {
return nil
}
return &models.Discount{
ID: discount.GetID(),
Name: discount.GetName(),
Layer: discount.GetLayer(),
Description: discount.GetDescription(),
Condition: *DiscountConditionProtoToModel(discount.GetCondition()),
Target: *DiscountCalculationTargetProtoToModel(discount.GetTarget()),
Audit: *AuditProtoToModel(discount.GetAudit()),
}
}
func DiscountsModelToProto(discounts []models.Discount) *proto.Discounts {
protoDiscounts := make([]*proto.Discount, len(discounts))
for index, discount := range discounts {
protoDiscounts[index] = DiscountModelToProto(discount)
}
return &proto.Discounts{
Discounts: protoDiscounts,
}
}
func DiscountModelToProto(discount models.Discount) *proto.Discount {
return &proto.Discount{
ID: discount.ID,
Name: discount.Name,
Layer: discount.Layer,
Description: discount.Description,
Condition: DiscountConditionModelToProto(&discount.Condition),
Target: DiscountCalculationTargetModelToProto(discount.Target),
Audit: AuditModelToProto(&discount.Audit),
}
}

@ -1,243 +1,242 @@
package transfer_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
func TestDiscountsProtoToModel(t *testing.T) {
cartPurchasesAmount := float64(20000)
purchasesAmount := float64(20000)
userID := string("13")
userType := string("type")
coupon := string("coupon1")
product := string("product1")
term := uint64(20)
t.Run("перевод массива скидок с proto в модель с nil", func(t *testing.T) {
assert.Equal(t, []*models.Discount{}, transfer.DiscountsProtoToModel(nil))
})
t.Run("перевод массива скидок с proto в модель", func(t *testing.T) {
assert.Equal(t,
[]*models.Discount{
{
ID: "1",
Name: "Скидка на объём в корзине 1",
Layer: 8,
Description: "",
Condition: models.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
PurchasesAmount: &purchasesAmount,
User: &userID,
UserType: &userType,
Coupon: &coupon,
Product: &product,
Term: &term,
Period: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(0, 0).UTC(),
},
},
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Products: []models.ProductTarget{},
Factor: 20,
},
},
},
transfer.DiscountsProtoToModel(&discount.Discounts{
Discounts: []*discount.Discount{
{
ID: "1",
Name: "Скидка на объём в корзине 1",
Layer: 8,
Description: "",
Condition: &discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
PurchasesAmount: &purchasesAmount,
User: &userID,
UserType: &userType,
Coupon: &coupon,
Product: &product,
Term: &term,
Period: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
},
To: &timestamppb.Timestamp{},
},
},
Target: &discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{},
Factor: 20,
},
},
},
}),
)
})
}
func TestDiscountProtoToModel(t *testing.T) {
cartPurchasesAmount := float64(20000)
deletedAtModel := time.Unix(1674546287, 0).UTC()
deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
t.Run("Перевод скидки с proto в модель", func(t *testing.T) {
assert.Nil(t, transfer.DiscountProtoToModel(nil))
})
t.Run("Перевод скидки с proto в модель", func(t *testing.T) {
assert.Equal(t,
&models.Discount{
ID: "1",
Name: "Скидка на объём в корзине 4",
Layer: 8,
Description: "",
Condition: models.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
},
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Products: []models.ProductTarget{},
Factor: 20,
},
Audit: models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
DeletedAt: &deletedAtModel,
Deleted: true,
CreatedAt: time.Unix(1674546287, 0).UTC(),
},
},
transfer.DiscountProtoToModel(&discount.Discount{
ID: "1",
Name: "Скидка на объём в корзине 4",
Layer: 8,
Description: "",
Condition: &discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
},
Target: &discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{},
Factor: 20,
},
Audit: &discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
DeletedAt: &deletedAtProto,
Deleted: true,
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
},
}),
)
})
}
func TestDiscountsModelToProto(t *testing.T) {
cartPurchasesAmount := float64(20000)
deletedAtModel := time.Unix(1674546287, 0).UTC()
deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
t.Run("Перевод массива скидок из модели в proto", func(t *testing.T) {
assert.Equal(t,
&discount.Discounts{Discounts: []*discount.Discount{
{
ID: "1",
Name: "Скидка на объём в корзине 5",
Layer: 8,
Description: "",
Condition: &discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
},
Target: &discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{},
Factor: 20,
Overhelm: new(bool),
TargetGroup: new(string),
},
Audit: &discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
DeletedAt: &deletedAtProto,
Deleted: true,
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
},
},
}},
transfer.DiscountsModelToProto([]models.Discount{
{
ID: "1",
Name: "Скидка на объём в корзине 5",
Layer: 8,
Description: "",
Condition: models.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
},
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Products: []models.ProductTarget{},
Factor: 20,
},
Audit: models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
DeletedAt: &deletedAtModel,
Deleted: true,
CreatedAt: time.Unix(1674546287, 0).UTC(),
},
},
}),
)
})
}
func TestDiscountModelToProto(t *testing.T) {
cartPurchasesAmount := float64(20000)
t.Run("Перевод скидки из модели в proto", func(t *testing.T) {
assert.Equal(t,
&discount.Discount{
ID: "1",
Name: "Скидка на объём в корзине 8",
Layer: 8,
Description: "",
Condition: &discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
},
Target: &discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{},
Factor: 20,
Overhelm: new(bool),
TargetGroup: new(string),
},
Audit: &discount.Audit{
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
},
},
transfer.DiscountModelToProto(models.Discount{
ID: "1",
Name: "Скидка на объём в корзине 8",
Layer: 8,
Description: "",
Condition: models.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
},
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Products: []models.ProductTarget{},
Factor: 20,
},
}),
)
})
}
package transfer_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
func TestDiscountsProtoToModel(t *testing.T) {
cartPurchasesAmount := uint64(20000)
purchasesAmount := uint64(20000)
userID := string("13")
userType := string("type")
coupon := string("coupon1")
product := string("product1")
term := uint64(20)
t.Run("перевод массива скидок с proto в модель с nil", func(t *testing.T) {
assert.Equal(t, []*models.Discount{}, transfer.DiscountsProtoToModel(nil))
})
t.Run("перевод массива скидок с proto в модель", func(t *testing.T) {
assert.Equal(t,
[]*models.Discount{
{
ID: "1",
Name: "Скидка на объём в корзине 1",
Layer: 8,
Description: "",
Condition: models.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
PurchasesAmount: &purchasesAmount,
User: &userID,
UserType: &userType,
Coupon: &coupon,
Product: &product,
Term: &term,
Period: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(0, 0).UTC(),
},
},
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Products: []models.ProductTarget{},
Factor: 20,
},
},
},
transfer.DiscountsProtoToModel(&discount.Discounts{
Discounts: []*discount.Discount{
{
ID: "1",
Name: "Скидка на объём в корзине 1",
Layer: 8,
Description: "",
Condition: &discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
PurchasesAmount: &purchasesAmount,
User: &userID,
UserType: &userType,
Coupon: &coupon,
Product: &product,
Term: &term,
Period: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
},
To: &timestamppb.Timestamp{},
},
},
Target: &discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{},
Factor: 20,
},
},
},
}),
)
})
}
func TestDiscountProtoToModel(t *testing.T) {
cartPurchasesAmount := uint64(20000)
deletedAtModel := time.Unix(1674546287, 0).UTC()
deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
t.Run("Перевод скидки с proto в модель", func(t *testing.T) {
assert.Nil(t, transfer.DiscountProtoToModel(nil))
})
t.Run("Перевод скидки с proto в модель", func(t *testing.T) {
assert.Equal(t,
&models.Discount{
ID: "1",
Name: "Скидка на объём в корзине 4",
Layer: 8,
Description: "",
Condition: models.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
},
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Products: []models.ProductTarget{},
Factor: 20,
},
Audit: models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
DeletedAt: &deletedAtModel,
Deleted: true,
CreatedAt: time.Unix(1674546287, 0).UTC(),
},
},
transfer.DiscountProtoToModel(&discount.Discount{
ID: "1",
Name: "Скидка на объём в корзине 4",
Layer: 8,
Description: "",
Condition: &discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
},
Target: &discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{},
Factor: 20,
},
Audit: &discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
DeletedAt: &deletedAtProto,
Deleted: true,
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
},
}),
)
})
}
func TestDiscountsModelToProto(t *testing.T) {
cartPurchasesAmount := uint64(20000)
deletedAtModel := time.Unix(1674546287, 0).UTC()
deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
t.Run("Перевод массива скидок из модели в proto", func(t *testing.T) {
assert.Equal(t,
&discount.Discounts{Discounts: []*discount.Discount{
{
ID: "1",
Name: "Скидка на объём в корзине 5",
Layer: 8,
Description: "",
Condition: &discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
},
Target: &discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{},
Factor: 20,
Overhelm: new(bool),
TargetGroup: new(string),
},
Audit: &discount.Audit{
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
DeletedAt: &deletedAtProto,
Deleted: true,
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
},
},
}},
transfer.DiscountsModelToProto([]models.Discount{
{
ID: "1",
Name: "Скидка на объём в корзине 5",
Layer: 8,
Description: "",
Condition: models.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
},
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Products: []models.ProductTarget{},
Factor: 20,
},
Audit: models.Audit{
UpdatedAt: time.Unix(1674546287, 0).UTC(),
DeletedAt: &deletedAtModel,
Deleted: true,
CreatedAt: time.Unix(1674546287, 0).UTC(),
},
},
}),
)
})
}
func TestDiscountModelToProto(t *testing.T) {
cartPurchasesAmount := uint64(20000)
t.Run("Перевод скидки из модели в proto", func(t *testing.T) {
assert.Equal(t,
&discount.Discount{
ID: "1",
Name: "Скидка на объём в корзине 8",
Layer: 8,
Description: "",
Condition: &discount.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
},
Target: &discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{},
Factor: 20,
Overhelm: new(bool),
TargetGroup: new(string),
},
Audit: &discount.Audit{
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
},
},
transfer.DiscountModelToProto(models.Discount{
ID: "1",
Name: "Скидка на объём в корзине 8",
Layer: 8,
Description: "",
Condition: models.DiscountCondition{
CartPurchasesAmount: &cartPurchasesAmount,
},
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Products: []models.ProductTarget{},
Factor: 20,
},
}),
)
})
}

@ -1,30 +1,29 @@
package transfer
import (
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
func PeriodModelToProto(perion *models.PeriodCondition) *proto.PeriodCondition {
if perion == nil {
return nil
}
return &proto.PeriodCondition{
From: timestamppb.New(perion.From),
To: timestamppb.New(perion.To),
}
}
func PeriodProtoToModel(perion *proto.PeriodCondition) *models.PeriodCondition {
if perion == nil {
return nil
}
return &models.PeriodCondition{
From: perion.GetFrom().AsTime(),
To: perion.GetTo().AsTime(),
}
}
package transfer
import (
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
func PeriodModelToProto(perion *models.PeriodCondition) *proto.PeriodCondition {
if perion == nil {
return nil
}
return &proto.PeriodCondition{
From: timestamppb.New(perion.From),
To: timestamppb.New(perion.To),
}
}
func PeriodProtoToModel(perion *proto.PeriodCondition) *models.PeriodCondition {
if perion == nil {
return nil
}
return &models.PeriodCondition{
From: perion.GetFrom().AsTime(),
To: perion.GetTo().AsTime(),
}
}

@ -1,129 +1,128 @@
package transfer_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
/*
TODO:
Переписать тесты с testCases в обычный список t.Run тестов
*/
func TestPeriodProtoToModel(t *testing.T) {
testCases := []struct {
name string
proto *discount.PeriodCondition
model *models.PeriodCondition
}{
{
name: "correct transfer period from proto to model",
proto: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
To: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
},
model: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
},
{
name: "correct transfer period with empty milliseconds from proto to model",
proto: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
},
To: &timestamppb.Timestamp{},
},
model: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(0, 0).UTC(),
},
},
{
name: "correct transfer period with empty fields from proto to model",
proto: &discount.PeriodCondition{
From: &timestamppb.Timestamp{},
},
model: &models.PeriodCondition{
From: time.Unix(0, 0).UTC(),
To: time.Unix(0, 0).UTC(),
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.model, transfer.PeriodProtoToModel(testCase.proto))
})
}
}
func TestPeriodModelToProto(t *testing.T) {
testCases := []struct {
name string
proto *discount.PeriodCondition
model *models.PeriodCondition
}{
{
name: "correct transfer period from model to proto",
proto: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
To: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
},
model: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
},
{
name: "correct transfer period with empty milliseconds from model to proto",
proto: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
},
To: &timestamppb.Timestamp{},
},
model: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(0, 0).UTC(),
},
},
{
name: "correct transfer period with empty fields from model to proto",
proto: &discount.PeriodCondition{
From: &timestamppb.Timestamp{},
To: timestamppb.New(time.Time{}),
},
model: &models.PeriodCondition{
From: time.Unix(0, 0).UTC(),
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.proto, transfer.PeriodModelToProto(testCase.model))
})
}
}
package transfer_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
/*
TODO:
Переписать тесты с testCases в обычный список t.Run тестов
*/
func TestPeriodProtoToModel(t *testing.T) {
testCases := []struct {
name string
proto *discount.PeriodCondition
model *models.PeriodCondition
}{
{
name: "correct transfer period from proto to model",
proto: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
To: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
},
model: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
},
{
name: "correct transfer period with empty milliseconds from proto to model",
proto: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
},
To: &timestamppb.Timestamp{},
},
model: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(0, 0).UTC(),
},
},
{
name: "correct transfer period with empty fields from proto to model",
proto: &discount.PeriodCondition{
From: &timestamppb.Timestamp{},
},
model: &models.PeriodCondition{
From: time.Unix(0, 0).UTC(),
To: time.Unix(0, 0).UTC(),
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.model, transfer.PeriodProtoToModel(testCase.proto))
})
}
}
func TestPeriodModelToProto(t *testing.T) {
testCases := []struct {
name string
proto *discount.PeriodCondition
model *models.PeriodCondition
}{
{
name: "correct transfer period from model to proto",
proto: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
To: &timestamppb.Timestamp{
Seconds: 1674546287,
Nanos: 0,
},
},
model: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(),
},
},
{
name: "correct transfer period with empty milliseconds from model to proto",
proto: &discount.PeriodCondition{
From: &timestamppb.Timestamp{
Seconds: 1674546287,
},
To: &timestamppb.Timestamp{},
},
model: &models.PeriodCondition{
From: time.Unix(1674546287, 0).UTC(),
To: time.Unix(0, 0).UTC(),
},
},
{
name: "correct transfer period with empty fields from model to proto",
proto: &discount.PeriodCondition{
From: &timestamppb.Timestamp{},
To: timestamppb.New(time.Time{}),
},
model: &models.PeriodCondition{
From: time.Unix(0, 0).UTC(),
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.proto, transfer.PeriodModelToProto(testCase.model))
})
}
}

@ -1,85 +1,85 @@
package transfer
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
func ProductTargetsProtoToModel(products []*proto.ProductTarget) []models.ProductTarget {
modelProducts := make([]models.ProductTarget, len(products))
for index, product := range products {
modelProducts[index] = *ProductTargetProtoToModel(product)
}
return modelProducts
}
func ProductTargetProtoToModel(product *proto.ProductTarget) *models.ProductTarget {
if product == nil {
return nil
}
receiveOverhelm := func() bool {
if product.Overhelm != nil {
return *product.Overhelm
}
return false
}
return &models.ProductTarget{
ID: product.ID,
Factor: product.Factor,
Overhelm: receiveOverhelm(),
}
}
func ProductTargetsModelToProto(products []models.ProductTarget) []*proto.ProductTarget {
protoProducts := make([]*proto.ProductTarget, len(products))
for index, product := range products {
protoProducts[index] = ProductTargetModelToProto(product)
}
return protoProducts
}
func ProductTargetModelToProto(product models.ProductTarget) *proto.ProductTarget {
return &proto.ProductTarget{
ID: product.ID,
Factor: product.Factor,
Overhelm: &product.Overhelm,
}
}
func ProductsProtoToCore(productInformations []*proto.ProductInformation) []core.Product {
products := make([]core.Product, len(productInformations))
if len(productInformations) < 1 {
return []core.Product{}
}
receiveGroup := func(product *proto.ProductInformation) string {
if product == nil || product.Group == nil {
return ""
}
return *product.Group
}
for index, product := range productInformations {
if product == nil {
continue
}
products[index] = core.Product{
ID: product.ID,
Price: product.Price,
Group: receiveGroup(product),
}
}
return products
}
package transfer
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
func ProductTargetsProtoToModel(products []*proto.ProductTarget) []models.ProductTarget {
modelProducts := make([]models.ProductTarget, len(products))
for index, product := range products {
modelProducts[index] = *ProductTargetProtoToModel(product)
}
return modelProducts
}
func ProductTargetProtoToModel(product *proto.ProductTarget) *models.ProductTarget {
if product == nil {
return nil
}
receiveOverhelm := func() bool {
if product.Overhelm != nil {
return *product.Overhelm
}
return false
}
return &models.ProductTarget{
ID: product.ID,
Factor: product.Factor,
Overhelm: receiveOverhelm(),
}
}
func ProductTargetsModelToProto(products []models.ProductTarget) []*proto.ProductTarget {
protoProducts := make([]*proto.ProductTarget, len(products))
for index, product := range products {
protoProducts[index] = ProductTargetModelToProto(product)
}
return protoProducts
}
func ProductTargetModelToProto(product models.ProductTarget) *proto.ProductTarget {
return &proto.ProductTarget{
ID: product.ID,
Factor: product.Factor,
Overhelm: &product.Overhelm,
}
}
func ProductsProtoToCore(productInformations []*proto.ProductInformation) []core.Product {
products := make([]core.Product, len(productInformations))
if len(productInformations) < 1 {
return []core.Product{}
}
receiveGroup := func(product *proto.ProductInformation) string {
if product == nil || product.Group == nil {
return ""
}
return *product.Group
}
for index, product := range productInformations {
if product == nil {
continue
}
products[index] = core.Product{
ID: product.ID,
Price: float64(product.Price),
Group: receiveGroup(product),
}
}
return products
}

@ -1,241 +1,240 @@
package transfer_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
/*
TODO:
1) Переписать тесты с testCases в обычный список t.Run тестов
2) Отформатировать массив объектов
3) Убрать глобальные переменные касающиеся других пакетов
*/
var (
overhelmTrue = true
)
func TestProductTargetsProtoToModel(t *testing.T) {
testName := "correct transfer product tagrets from proto to model"
proto := []*discount.ProductTarget{
{
ID: "1",
Factor: 20,
},
{
ID: "2",
Factor: 20,
Overhelm: &overhelmTrue,
},
{
ID: "3",
Factor: 10,
},
}
model := []models.ProductTarget{
{
ID: "1",
Factor: 20,
},
{
ID: "2",
Factor: 20,
Overhelm: true,
},
{
ID: "3",
Factor: 10,
},
}
t.Run(testName, func(t *testing.T) {
assert.Equal(t, model, transfer.ProductTargetsProtoToModel(proto))
})
}
func TestProductTargetProtoToModel(t *testing.T) {
testCases := []struct {
name string
proto *discount.ProductTarget
model *models.ProductTarget
}{
{
name: "correct transfer product tagret from proto to model",
proto: &discount.ProductTarget{
ID: "1",
Factor: 20,
},
model: &models.ProductTarget{
ID: "1",
Factor: 20,
},
},
{
name: "correct transfer nil product tagret from proto to model",
proto: nil,
model: nil,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.model, transfer.ProductTargetProtoToModel(testCase.proto))
})
}
}
func TestProductTargetsModelToProto(t *testing.T) {
testName := "correct transfer product tagrets from model to proto"
proto := []*discount.ProductTarget{
{
ID: "1",
Factor: 50,
Overhelm: &overhelmTrue,
},
{
ID: "2",
Factor: 20,
Overhelm: new(bool),
},
{
ID: "3",
Factor: 10,
Overhelm: new(bool),
},
}
model := []models.ProductTarget{
{
ID: "1",
Factor: 50,
Overhelm: true,
},
{
ID: "2",
Factor: 20,
},
{
ID: "3",
Factor: 10,
},
}
t.Run(testName, func(t *testing.T) {
assert.Equal(t, proto, transfer.ProductTargetsModelToProto(model))
})
}
func TestProductTargetModelToProto(t *testing.T) {
testCases := []struct {
name string
proto *discount.ProductTarget
model models.ProductTarget
}{
{
name: "correct transfer product tagret from model to proto",
proto: &discount.ProductTarget{
ID: "1",
Factor: 20,
Overhelm: new(bool),
},
model: models.ProductTarget{
ID: "1",
Factor: 20,
},
},
{
name: "correct transfer nil product tagret from model to proto",
proto: &discount.ProductTarget{
Overhelm: new(bool),
},
model: models.ProductTarget{},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.proto, transfer.ProductTargetModelToProto(testCase.model))
})
}
}
func TestProductsProtoToCore(t *testing.T) {
group := "testGroup"
testCases := []struct {
name string
proto []*discount.ProductInformation
core []core.Product
}{
{
name: "transfer product informations from proto to core",
proto: []*discount.ProductInformation{
{
ID: "1",
Price: 20,
Group: &group,
},
{
ID: "2",
Price: 30,
},
},
core: []core.Product{
{
ID: "1",
Price: 20,
Group: group,
},
{
ID: "2",
Price: 30,
},
},
},
{
name: "transfer empty product informations from proto to core",
proto: []*discount.ProductInformation{},
core: []core.Product{},
},
{
name: "transfer product informations from proto to core with nil proto",
proto: []*discount.ProductInformation{
{
ID: "1",
Price: 20,
Group: &group,
},
{
ID: "2",
Price: 30,
},
nil,
},
core: []core.Product{
{
ID: "1",
Price: 20,
Group: group,
},
{
ID: "2",
Price: 30,
},
{},
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.core, transfer.ProductsProtoToCore(testCase.proto))
})
}
}
package transfer_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
)
/*
TODO:
1) Переписать тесты с testCases в обычный список t.Run тестов
2) Отформатировать массив объектов
3) Убрать глобальные переменные касающиеся других пакетов
*/
var (
overhelmTrue = true
)
func TestProductTargetsProtoToModel(t *testing.T) {
testName := "correct transfer product tagrets from proto to model"
proto := []*discount.ProductTarget{
{
ID: "1",
Factor: 20,
},
{
ID: "2",
Factor: 20,
Overhelm: &overhelmTrue,
},
{
ID: "3",
Factor: 10,
},
}
model := []models.ProductTarget{
{
ID: "1",
Factor: 20,
},
{
ID: "2",
Factor: 20,
Overhelm: true,
},
{
ID: "3",
Factor: 10,
},
}
t.Run(testName, func(t *testing.T) {
assert.Equal(t, model, transfer.ProductTargetsProtoToModel(proto))
})
}
func TestProductTargetProtoToModel(t *testing.T) {
testCases := []struct {
name string
proto *discount.ProductTarget
model *models.ProductTarget
}{
{
name: "correct transfer product tagret from proto to model",
proto: &discount.ProductTarget{
ID: "1",
Factor: 20,
},
model: &models.ProductTarget{
ID: "1",
Factor: 20,
},
},
{
name: "correct transfer nil product tagret from proto to model",
proto: nil,
model: nil,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.model, transfer.ProductTargetProtoToModel(testCase.proto))
})
}
}
func TestProductTargetsModelToProto(t *testing.T) {
testName := "correct transfer product tagrets from model to proto"
proto := []*discount.ProductTarget{
{
ID: "1",
Factor: 50,
Overhelm: &overhelmTrue,
},
{
ID: "2",
Factor: 20,
Overhelm: new(bool),
},
{
ID: "3",
Factor: 10,
Overhelm: new(bool),
},
}
model := []models.ProductTarget{
{
ID: "1",
Factor: 50,
Overhelm: true,
},
{
ID: "2",
Factor: 20,
},
{
ID: "3",
Factor: 10,
},
}
t.Run(testName, func(t *testing.T) {
assert.Equal(t, proto, transfer.ProductTargetsModelToProto(model))
})
}
func TestProductTargetModelToProto(t *testing.T) {
testCases := []struct {
name string
proto *discount.ProductTarget
model models.ProductTarget
}{
{
name: "correct transfer product tagret from model to proto",
proto: &discount.ProductTarget{
ID: "1",
Factor: 20,
Overhelm: new(bool),
},
model: models.ProductTarget{
ID: "1",
Factor: 20,
},
},
{
name: "correct transfer nil product tagret from model to proto",
proto: &discount.ProductTarget{
Overhelm: new(bool),
},
model: models.ProductTarget{},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.proto, transfer.ProductTargetModelToProto(testCase.model))
})
}
}
func TestProductsProtoToCore(t *testing.T) {
group := "testGroup"
testCases := []struct {
name string
proto []*discount.ProductInformation
core []core.Product
}{
{
name: "transfer product informations from proto to core",
proto: []*discount.ProductInformation{
{
ID: "1",
Price: 20,
Group: &group,
},
{
ID: "2",
Price: 30,
},
},
core: []core.Product{
{
ID: "1",
Price: 20,
Group: group,
},
{
ID: "2",
Price: 30,
},
},
},
{
name: "transfer empty product informations from proto to core",
proto: []*discount.ProductInformation{},
core: []core.Product{},
},
{
name: "transfer product informations from proto to core with nil proto",
proto: []*discount.ProductInformation{
{
ID: "1",
Price: 20,
Group: &group,
},
{
ID: "2",
Price: 30,
},
nil,
},
core: []core.Product{
{
ID: "1",
Price: 20,
Group: group,
},
{
ID: "2",
Price: 30,
},
{},
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.core, transfer.ProductsProtoToCore(testCase.proto))
})
}
}

@ -1 +1 @@
[{ "delete": "discounts", "deletes": [{ "q": {} }] }]
[{ "delete": "discounts", "deletes": [{ "q": {} }] }]

@ -1,486 +1,486 @@
[
{
"insert": "discounts",
"ordered": true,
"documents": [
{
"layer": 4,
"name": "Лояльность 1",
"description": "постоянная скидка для юзеров, внёсших на проект от 10 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине",
"condition": {
"purchasesAmount": 10000
},
"target": {
"factor": 0.99
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Лояльность 2",
"description": "постоянная скидка для юзеров, внёсших на проект от 25 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине",
"layer": 4,
"condition": {
"purchasesAmount": 25000
},
"target": {
"factor": 0.99
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Лояльность 3",
"description": "постоянная скидка для юзеров, внёсших на проект от 50 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине",
"layer": 4,
"condition": {
"purchasesAmount": 50000
},
"target": {
"factor": 0.975
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Корзина 1",
"description": "Скидка на размер корзины от 5 000 р. Применяется на итоговую сумму, после суммирования корзины",
"layer": 3,
"condition": {
"cartPurchasesAmount": 5000
},
"target": {
"scope": "sum",
"factor": 0.985
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Корзина 2",
"description": "Скидка на размер корзины от 50 000 р. Применяется на итоговую сумму, после суммирования корзины",
"layer": 3,
"condition": {
"cartPurchasesAmount": 50000
},
"target": {
"scope": "sum",
"factor": 0.965
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "НКО",
"description": "Скидка всем подтвердившим статус НКО. Перекрывает ВСЕ остальные скидки. Если эта скидка срабатывает, остальные можно не вычислять. Т.е. если на уровне 0 находится какая-лидо скидка для выданных условий, просто суммируем корзину и применяем к сумме указанный множитель, после чего прекращаем процесс рассчёта",
"layer": 1,
"condition": {
"userType": "nko"
},
"target": {
"scope": "sum",
"factor": 0.2,
"overhelm": true
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Промокод На АБ тесты",
"description": "Скидка, полученная конкретным юзером, после введения промокода. Заменяет собой не промокодовую",
"layer": 2,
"condition": {
"coupon": "coupon1",
"user": "buddy"
},
"target": {
"overhelm": true,
"scope": "each",
"products": [
{
"productId": "p6",
"factor": 0.5
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Новогодняя скидка",
"description": "",
"layer": 3,
"condition": {
"period": {
"from": "2022-12-31T00:00:00.000Z",
"to": "2024-01-01T00:00:00.000Z"
}
},
"target": {
"scope": "each",
"products": [
{
"productId": "p1",
"factor": 0.965
},
{
"productId": "p2",
"factor": 0.5
},
{
"productId": "p3",
"factor": 0.8
},
{
"productId": "p4",
"factor": 0.95
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Скидка на определённого пользователя",
"description": "",
"layer": 3,
"condition": {
"user": "another_buddy"
},
"target": {
"scope": "sum",
"factor": 0.95
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Анлим Шабло 1",
"description": "Скидка на количество безлимитных дней работы от 30 дней",
"layer": 3,
"condition": {
"term": 30,
"product": "p1"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p1",
"factor": 0.975
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Анлим Шабло 2",
"description": "Скидка на количество безлимитных дней работы от 90 дней",
"layer": 3,
"condition": {
"term": 90,
"product": "p1"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p1",
"factor": 0.975
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Анлим Шабло 3",
"description": "Скидка на количество безлимитных дней работы от 180 дней",
"layer": 1,
"condition": {
"term": 180,
"product": "p1"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p1",
"factor": 0.93
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Генерации Шабло 1",
"description": "Скидка на количество генераций от 100 шт",
"layer": 3,
"condition": {
"usage": 100,
"product": "p2"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p2",
"factor": 0.995
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Генерации Шабло 2",
"description": "Скидка на количество генераций от 350 шт",
"layer": 3,
"condition": {
"usage": 350,
"product": "p2"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p2",
"factor": 0.98
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Генерации Шабло 3",
"description": "Скидка на количество генераций от 500 шт",
"layer": 3,
"condition": {
"usage": 500,
"product": "p2"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p2",
"factor": 0.945
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Шаблонизатор 1",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису шаблонизации, от 1000 р",
"layer": 3,
"condition": {
"group": "templategen",
"priceFrom": 1000
},
"target": {
"scope": "group",
"group": "templategen",
"factor": 0.996
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Шаблонизатор 2",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису шаблонизации, от 5000 р",
"layer": 3,
"condition": {
"group": "templategen",
"priceFrom": 5000
},
"target": {
"scope": "group",
"group": "templategen",
"factor": 0.983
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Опросник 1",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису опросника, от 2000 р",
"layer": 3,
"condition": {
"group": "squiz",
"priceFrom": 2000
},
"target": {
"scope": "group",
"group": "squiz",
"factor": 0.983
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Опросник 2",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису опросника, от 6000 р",
"layer": 3,
"condition": {
"group": "squiz",
"priceFrom": 6000
},
"target": {
"scope": "group",
"group": "squiz",
"factor": 0.969
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Сокращатель 1",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису сокращателя, от 500 р",
"layer": 3,
"condition": {
"group": "dwarfener",
"priceFrom": 500
},
"target": {
"scope": "group",
"group": "dwarfener",
"factor": 0.99
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Сокращатель 2",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису сокращателя, от 2500 р",
"layer": 3,
"condition": {
"group": "dwarfener",
"priceFrom": 2500
},
"target": {
"scope": "group",
"group": "dwarfener",
"factor": 0.96
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"_id": {
"$oid": "641b2d73e0e07a7e90b59616"
},
"name": "Анлим Квиз 1",
"description": "Скидка на количество дней безлимитного использования опросника, от 30 дней",
"layer": 3,
"condition": {
"term": 30,
"product": "p3"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p3",
"factor": 0.97
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
}
]
}
]
[
{
"insert": "discounts",
"ordered": true,
"documents": [
{
"layer": 4,
"name": "Лояльность 1",
"description": "постоянная скидка для юзеров, внёсших на проект от 10 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине",
"condition": {
"purchasesAmount": 10000
},
"target": {
"factor": 0.99
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Лояльность 2",
"description": "постоянная скидка для юзеров, внёсших на проект от 25 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине",
"layer": 4,
"condition": {
"purchasesAmount": 25000
},
"target": {
"factor": 0.99
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Лояльность 3",
"description": "постоянная скидка для юзеров, внёсших на проект от 50 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине",
"layer": 4,
"condition": {
"purchasesAmount": 50000
},
"target": {
"factor": 0.975
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Корзина 1",
"description": "Скидка на размер корзины от 5 000 р. Применяется на итоговую сумму, после суммирования корзины",
"layer": 3,
"condition": {
"cartPurchasesAmount": 5000
},
"target": {
"scope": "sum",
"factor": 0.985
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Корзина 2",
"description": "Скидка на размер корзины от 50 000 р. Применяется на итоговую сумму, после суммирования корзины",
"layer": 3,
"condition": {
"cartPurchasesAmount": 50000
},
"target": {
"scope": "sum",
"factor": 0.965
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "НКО",
"description": "Скидка всем подтвердившим статус НКО. Перекрывает ВСЕ остальные скидки. Если эта скидка срабатывает, остальные можно не вычислять. Т.е. если на уровне 0 находится какая-лидо скидка для выданных условий, просто суммируем корзину и применяем к сумме указанный множитель, после чего прекращаем процесс рассчёта",
"layer": 1,
"condition": {
"userType": "nko"
},
"target": {
"scope": "sum",
"factor": 0.2,
"overhelm": true
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Промокод На АБ тесты",
"description": "Скидка, полученная конкретным юзером, после введения промокода. Заменяет собой не промокодовую",
"layer": 2,
"condition": {
"coupon": "coupon1",
"user": "buddy"
},
"target": {
"overhelm": true,
"scope": "each",
"products": [
{
"productId": "p6",
"factor": 0.5
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Новогодняя скидка",
"description": "",
"layer": 3,
"condition": {
"period": {
"from": "2022-12-31T00:00:00.000Z",
"to": "2024-01-01T00:00:00.000Z"
}
},
"target": {
"scope": "each",
"products": [
{
"productId": "p1",
"factor": 0.965
},
{
"productId": "p2",
"factor": 0.5
},
{
"productId": "p3",
"factor": 0.8
},
{
"productId": "p4",
"factor": 0.95
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Скидка на определённого пользователя",
"description": "",
"layer": 3,
"condition": {
"user": "another_buddy"
},
"target": {
"scope": "sum",
"factor": 0.95
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Анлим Шабло 1",
"description": "Скидка на количество безлимитных дней работы от 30 дней",
"layer": 3,
"condition": {
"term": 30,
"product": "p1"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p1",
"factor": 0.975
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Анлим Шабло 2",
"description": "Скидка на количество безлимитных дней работы от 90 дней",
"layer": 3,
"condition": {
"term": 90,
"product": "p1"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p1",
"factor": 0.975
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Анлим Шабло 3",
"description": "Скидка на количество безлимитных дней работы от 180 дней",
"layer": 1,
"condition": {
"term": 180,
"product": "p1"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p1",
"factor": 0.93
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Генерации Шабло 1",
"description": "Скидка на количество генераций от 100 шт",
"layer": 3,
"condition": {
"usage": 100,
"product": "p2"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p2",
"factor": 0.995
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Генерации Шабло 2",
"description": "Скидка на количество генераций от 350 шт",
"layer": 3,
"condition": {
"usage": 350,
"product": "p2"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p2",
"factor": 0.98
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Генерации Шабло 3",
"description": "Скидка на количество генераций от 500 шт",
"layer": 3,
"condition": {
"usage": 500,
"product": "p2"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p2",
"factor": 0.945
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Шаблонизатор 1",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису шаблонизации, от 1000 р",
"layer": 3,
"condition": {
"group": "templategen",
"priceFrom": 1000
},
"target": {
"scope": "group",
"group": "templategen",
"factor": 0.996
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Шаблонизатор 2",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису шаблонизации, от 5000 р",
"layer": 3,
"condition": {
"group": "templategen",
"priceFrom": 5000
},
"target": {
"scope": "group",
"group": "templategen",
"factor": 0.983
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Опросник 1",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису опросника, от 2000 р",
"layer": 3,
"condition": {
"group": "squiz",
"priceFrom": 2000
},
"target": {
"scope": "group",
"group": "squiz",
"factor": 0.983
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Опросник 2",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису опросника, от 6000 р",
"layer": 3,
"condition": {
"group": "squiz",
"priceFrom": 6000
},
"target": {
"scope": "group",
"group": "squiz",
"factor": 0.969
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Сокращатель 1",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису сокращателя, от 500 р",
"layer": 3,
"condition": {
"group": "dwarfener",
"priceFrom": 500
},
"target": {
"scope": "group",
"group": "dwarfener",
"factor": 0.99
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"name": "Сокращатель 2",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису сокращателя, от 2500 р",
"layer": 3,
"condition": {
"group": "dwarfener",
"priceFrom": 2500
},
"target": {
"scope": "group",
"group": "dwarfener",
"factor": 0.96
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
},
{
"_id": {
"$oid": "641b2d73e0e07a7e90b59616"
},
"name": "Анлим Квиз 1",
"description": "Скидка на количество дней безлимитного использования опросника, от 30 дней",
"layer": 3,
"condition": {
"term": 30,
"product": "p3"
},
"target": {
"scope": "each",
"products": [
{
"productId": "p3",
"factor": 0.97
}
]
},
"deprecated": false,
"audit": {
"createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false
}
}
]
}
]

@ -1,15 +1,15 @@
package array
func Contains[T any](array []T, callback func(T, int, []T) bool) bool {
isContains := false
for index, element := range array {
if callback(element, index, array) {
isContains = true
break
}
}
return isContains
}
package array
func Contains[T any](array []T, callback func(T, int, []T) bool) bool {
isContains := false
for index, element := range array {
if callback(element, index, array) {
isContains = true
break
}
}
return isContains
}

@ -1,153 +1,153 @@
package array_test
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/array"
)
func TestContains(t *testing.T) {
testCasesWithPrimitives := []struct {
name string
inputArray []any
inputCallback func(any, int, []any) bool
expect bool
}{
{
name: "Проверка наличие строк по значению",
inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"})
return element == "test1"
},
expect: true,
},
{
name: "Проверка наличие строк по значению (неудачная)",
inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"})
return element == "tttt"
},
expect: false,
},
{
name: "Проверка наличие строк по индексу",
inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"})
return index == 1
},
expect: true,
},
{
name: "Проверка наличие чисел по значению",
inputArray: []any{1, 4},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{1, 4})
return element == 1
},
expect: true,
},
{
name: "Проверка наличие строк по значению с несколькими схожими значениями",
inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"})
return strings.Contains(element.(string), "test")
},
expect: true,
},
}
testCasesWithObjects := []struct {
name string
inputArray []struct{ Name string }
inputCallback func(struct{ Name string }, int, []struct{ Name string }) bool
expect bool
}{
{
name: "Проверка наличие объектов по индексу",
inputArray: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
})
return index == 1
},
expect: true,
},
{
name: "Проверка наличие объектов по имени (неудачная)",
inputArray: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
})
return element.Name == "tttt"
},
expect: false,
},
{
name: "Проверка наличие объектов по значению поля",
inputArray: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
})
return element.Name == "test1"
},
expect: true,
},
{
name: "Проверка наличие объектов по совпадению значения поля",
inputArray: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
})
return strings.Contains(element.Name, "test")
},
expect: true,
},
}
for _, test := range testCasesWithPrimitives {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expect, array.Contains(test.inputArray, test.inputCallback))
})
}
for _, test := range testCasesWithObjects {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expect, array.Contains(test.inputArray, test.inputCallback))
})
}
}
package array_test
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/array"
)
func TestContains(t *testing.T) {
testCasesWithPrimitives := []struct {
name string
inputArray []any
inputCallback func(any, int, []any) bool
expect bool
}{
{
name: "Проверка наличие строк по значению",
inputArray: []any{"test1245", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1245", "test2"})
return element == "test1245"
},
expect: true,
},
{
name: "Проверка наличие строк по значению (неудачная)",
inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"})
return element == "fdadadfadfadf"
},
expect: false,
},
{
name: "Проверка наличие строк по индексу",
inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"})
return index == 1
},
expect: true,
},
{
name: "Проверка наличие чисел по значению",
inputArray: []any{1, 4},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{1, 4})
return element == 1
},
expect: true,
},
{
name: "Проверка наличие строк по значению с несколькими схожими значениями",
inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"})
return strings.Contains(element.(string), "test")
},
expect: true,
},
}
testCasesWithObjects := []struct {
name string
inputArray []struct{ Name string }
inputCallback func(struct{ Name string }, int, []struct{ Name string }) bool
expect bool
}{
{
name: "Проверка наличие объектов по индексу",
inputArray: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
})
return index == 1
},
expect: true,
},
{
name: "Проверка наличие объектов по имени (неудачная)",
inputArray: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
})
return element.Name == "tttt"
},
expect: false,
},
{
name: "Проверка наличие объектов по значению поля",
inputArray: []struct{ Name string }{
{Name: "test19999"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test19999"},
{Name: "test2"},
})
return element.Name == "test19999"
},
expect: true,
},
{
name: "Проверка наличие объектов по совпадению значения поля",
inputArray: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
})
return strings.Contains(element.Name, "test")
},
expect: true,
},
}
for _, test := range testCasesWithPrimitives {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expect, array.Contains(test.inputArray, test.inputCallback))
})
}
for _, test := range testCasesWithObjects {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expect, array.Contains(test.inputArray, test.inputCallback))
})
}
}

@ -11,4 +11,3 @@ func Filter[T any](array []T, callback func(T, int, []T) bool) []T {
return filteredArray
}

@ -1,134 +1,134 @@
package array_test
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/array"
)
func TestFilter(t *testing.T) {
testCasesWithPrimitives := []struct {
name string
inputArray []any
inputCallback func(any, int, []any) bool
expect []any
}{
{
name: "Фильтрация массива строк по значению",
inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"})
return element == "test1"
},
expect: []any{"test1"},
},
{
name: "Фильтрация массива строк по индексу",
inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"})
return index == 1
},
expect: []any{"test2"},
},
{
name: "Фильтрация массива чисел по значению",
inputArray: []any{1, 4},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{1, 4})
return element == 1
},
expect: []any{1},
},
{
name: "Фильтрация массива строк по значению с несколькими схожими значениями",
inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"})
return strings.Contains(element.(string), "test")
},
expect: []any{"test1", "test2"},
},
}
testCasesWithObjects := []struct {
name string
inputArray []struct{ Name string }
inputCallback func(struct{ Name string }, int, []struct{ Name string }) bool
expect []struct{ Name string }
}{
{
name: "Фильтрация массива объектов по индексу",
inputArray: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
})
return index == 1
},
expect: []struct{ Name string }{
{Name: "test2"},
},
},
{
name: "Фильтрация массива объектов по значению поля",
inputArray: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
})
return element.Name == "test1"
},
expect: []struct{ Name string }{
{Name: "test1"},
},
},
{
name: "Фильтрация массива объектов по совпадению значения поля",
inputArray: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
})
return strings.Contains(element.Name, "test")
},
expect: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
},
}
for _, test := range testCasesWithPrimitives {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expect, array.Filter(test.inputArray, test.inputCallback))
})
}
for _, test := range testCasesWithObjects {
t.Run(test.name, func(t *testing.T) {
assert.ElementsMatch(t, test.expect, array.Filter(test.inputArray, test.inputCallback))
})
}
}
package array_test
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/array"
)
func TestFilter(t *testing.T) {
testCasesWithPrimitives := []struct {
name string
inputArray []any
inputCallback func(any, int, []any) bool
expect []any
}{
{
name: "Фильтрация массива строк по значению",
inputArray: []any{"test10130153", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test10130153", "test2"})
return element == "test10130153"
},
expect: []any{"test10130153"},
},
{
name: "Фильтрация массива строк по индексу",
inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"})
return index == 1
},
expect: []any{"test2"},
},
{
name: "Фильтрация массива чисел по значению",
inputArray: []any{1, 4},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{1, 4})
return element == 1
},
expect: []any{1},
},
{
name: "Фильтрация массива строк по значению с несколькими схожими значениями",
inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"})
return strings.Contains(element.(string), "test")
},
expect: []any{"test1", "test2"},
},
}
testCasesWithObjects := []struct {
name string
inputArray []struct{ Name string }
inputCallback func(struct{ Name string }, int, []struct{ Name string }) bool
expect []struct{ Name string }
}{
{
name: "Фильтрация массива объектов по индексу",
inputArray: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
})
return index == 1
},
expect: []struct{ Name string }{
{Name: "test2"},
},
},
{
name: "Фильтрация массива объектов по значению поля",
inputArray: []struct{ Name string }{
{Name: "test5313131"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test5313131"},
{Name: "test2"},
})
return element.Name == "test5313131"
},
expect: []struct{ Name string }{
{Name: "test5313131"},
},
},
{
name: "Фильтрация массива объектов по совпадению значения поля",
inputArray: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
})
return strings.Contains(element.Name, "test")
},
expect: []struct{ Name string }{
{Name: "test1"},
{Name: "test2"},
},
},
}
for _, test := range testCasesWithPrimitives {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expect, array.Filter(test.inputArray, test.inputCallback))
})
}
for _, test := range testCasesWithObjects {
t.Run(test.name, func(t *testing.T) {
assert.ElementsMatch(t, test.expect, array.Filter(test.inputArray, test.inputCallback))
})
}
}

11
pkg/array/find.go Normal file

@ -0,0 +1,11 @@
package array
func Find[T any](array []T, callback func(T, int, []T) bool) *T {
for index, element := range array {
if callback(element, index, array) {
return &element
}
}
return nil
}

32
pkg/array/find_test.go Normal file

@ -0,0 +1,32 @@
package array_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/array"
)
func TestFind(t *testing.T) {
t.Run("Find element in array (success)", func(t *testing.T) {
result := "test1"
callback := func(element string, index int, array []string) bool {
assert.Equal(t, array, []string{"test1", "test2"})
return element == result
}
assert.Equal(t, &result, array.Find([]string{"test1", "test2"}, callback))
})
t.Run("Find element in array (failure)", func(t *testing.T) {
result := "test"
callback := func(element string, index int, array []string) bool {
assert.Equal(t, array, []string{"test1", "test2"})
return element == result
}
assert.Nil(t, array.Find([]string{"test1", "test2"}, callback))
})
}

50
pkg/closer/closer.go Normal file

@ -0,0 +1,50 @@
package closer
import (
"context"
"fmt"
"log"
"sync"
)
type Callback func(ctx context.Context) error
type Closer struct {
mutex sync.Mutex
callbacks []Callback
}
func New() *Closer {
return &Closer{}
}
func (receiver *Closer) Add(callback Callback) {
receiver.mutex.Lock()
defer receiver.mutex.Unlock()
receiver.callbacks = append(receiver.callbacks, callback)
}
func (receiver *Closer) Close(ctx context.Context) error {
receiver.mutex.Lock()
defer receiver.mutex.Unlock()
complete := make(chan struct{}, 1)
go func() {
for index, callback := range receiver.callbacks {
if err := callback(ctx); err != nil {
log.Printf("[! (%d)] %v", index, err)
}
}
complete <- struct{}{}
}()
select {
case <-complete:
return nil
case <-ctx.Done():
return fmt.Errorf("shutdown cancelled: %v", ctx.Err())
}
}

@ -1,29 +1,29 @@
package convert
import "strconv"
func StringArrayToIntSlice(stringArray []string) []int {
intArray := make([]int, 0, len(stringArray))
for _, stringValue := range stringArray {
intValue, err := strconv.Atoi(stringValue)
if err != nil {
continue
}
intArray = append(intArray, intValue)
}
return intArray
}
func MapToArray[T any, V comparable](currentMap map[V]T) []T {
result := make([]T, len(currentMap))
index := 0
for _, element := range currentMap {
result[index] = element
index++
}
return result
}
package convert
import "strconv"
func StringArrayToIntSlice(stringArray []string) []int {
intArray := make([]int, 0, len(stringArray))
for _, stringValue := range stringArray {
intValue, err := strconv.Atoi(stringValue)
if err != nil {
continue
}
intArray = append(intArray, intValue)
}
return intArray
}
func MapToArray[T any, V comparable](currentMap map[V]T) []T {
result := make([]T, len(currentMap))
index := 0
for _, element := range currentMap {
result[index] = element
index++
}
return result
}

@ -1,22 +1,22 @@
package convert
func ArrayToMap[T any, V comparable](array []T, key func(T) V) map[V]T {
result := make(map[V]T, len(array))
for _, element := range array {
result[key(element)] = element
}
return result
}
func MapToMapArray[T any, V comparable](currentMap map[V]T, key func(T) V) map[V][]T {
result := make(map[V][]T)
for _, element := range currentMap {
currentKey := key(element)
result[currentKey] = append(result[currentKey], element)
}
return result
}
package convert
func ArrayToMap[T any, V comparable](array []T, key func(T) V) map[V]T {
result := make(map[V]T, len(array))
for _, element := range array {
result[key(element)] = element
}
return result
}
func MapToMapArray[T any, V comparable](currentMap map[V]T, key func(T) V) map[V][]T {
result := make(map[V][]T)
for _, element := range currentMap {
currentKey := key(element)
result[currentKey] = append(result[currentKey], element)
}
return result
}

74
pkg/convert/map_test.go Normal file

@ -0,0 +1,74 @@
package convert_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert"
)
func TestArrayToMap(t *testing.T) {
type Product struct {
ID string
Price float64
Group string
}
t.Run("Успешная конвертация массива в карту по ID", func(t *testing.T) {
products := []Product{
{ID: "p1", Price: 1500, Group: "templategen"},
{ID: "p2", Price: 300, Group: "templategen"},
{ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"},
}
assert.Equal(t,
map[string]Product{
"p1": {ID: "p1", Price: 1500, Group: "templategen"},
"p2": {ID: "p2", Price: 300, Group: "templategen"},
"p5": {ID: "p5", Price: 30, Group: "dwarfener"},
"p6": {ID: "p6", Price: 70, Group: "dwarfener"},
"p7": {ID: "p7", Price: 800, Group: "dwarfener"},
},
convert.ArrayToMap(products, func(product Product) string {
return product.ID
}),
)
})
}
func TestMapToMapArray(t *testing.T) {
type Product struct {
ID string
Price float64
Group string
}
t.Run("Успешная конвертация массива в карту из массивов", func(t *testing.T) {
productsMap := map[string]Product{
"p1": {ID: "p1", Price: 1500, Group: "templategen"},
"p2": {ID: "p2", Price: 300, Group: "templategen"},
"p5": {ID: "p5", Price: 30, Group: "dwarfener"},
"p6": {ID: "p6", Price: 70, Group: "dwarfener"},
"p7": {ID: "p7", Price: 800, Group: "dwarfener"},
}
assert.EqualValues(t,
map[string][]Product{
"templategen": {
{ID: "p1", Price: 1500, Group: "templategen"},
{ID: "p2", Price: 300, Group: "templategen"},
},
"dwarfener": {
{ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"},
},
},
convert.MapToMapArray(productsMap, func(product Product) string {
return product.Group
}),
)
})
}

@ -1,60 +1,58 @@
package convert
import (
"fmt"
"reflect"
)
func ObjectToMap[T ~map[string]interface{}](object interface{}, tagName string) (T, error) {
defer func() {
if recoveredError := recover(); recoveredError != nil {
fmt.Println("recovered from ObjectToMap: ", recoveredError)
}
}()
values := reflect.ValueOf(object)
if values.Kind() == reflect.Ptr {
values = values.Elem()
}
if values.Kind() != reflect.Struct {
return nil, fmt.Errorf("ObjectToMap only accepts structs; got %T", values)
}
fieldsCount := values.NumField()
resultMap := make(T, fieldsCount)
valueType := values.Type()
for index := 0; index < fieldsCount; index++ {
fieldType := valueType.Field(index)
field := values.Field(index)
if field.IsZero() {
continue
}
if tagValue := fieldType.Tag.Get(tagName); tagValue != "" {
resultMap[tagValue] = field.Interface()
}
}
return resultMap, nil
}
func ObjectToStringMap(object interface{}, tagName string) (map[string]string, error) {
mapObject, err := ObjectToMap[map[string]interface{}](object, tagName)
if err != nil {
return nil, err
}
mapString := make(map[string]string, len(mapObject))
for key, value := range mapObject {
strKey := fmt.Sprintf("%v", key)
strValue := fmt.Sprintf("%v", value)
mapString[strKey] = strValue
}
return mapString, nil
}
package convert
import (
"fmt"
"reflect"
)
func ObjectToMap[T ~map[string]interface{}](object interface{}, tagName string) (T, error) {
defer func() {
if recoveredError := recover(); recoveredError != nil {
fmt.Println("recovered from ObjectToMap: ", recoveredError)
}
}()
values := reflect.ValueOf(object)
if values.Kind() == reflect.Ptr {
values = values.Elem()
}
if values.Kind() != reflect.Struct {
return nil, fmt.Errorf("ObjectToMap only accepts structs; got %T", values)
}
fieldsCount := values.NumField()
resultMap := make(T, fieldsCount)
valueType := values.Type()
for index := 0; index < fieldsCount; index++ {
fieldType := valueType.Field(index)
field := values.Field(index)
if field.IsZero() {
continue
}
if tagValue := fieldType.Tag.Get(tagName); tagValue != "" {
resultMap[tagValue] = field.Interface()
}
}
return resultMap, nil
}
func ObjectToStringMap(object interface{}, tagName string) (map[string]string, error) {
mapObject, err := ObjectToMap[map[string]interface{}](object, tagName)
if err != nil {
return nil, err
}
mapString := make(map[string]string, len(mapObject))
for key, value := range mapObject {
strValue := fmt.Sprintf("%v", value)
mapString[key] = strValue
}
return mapString, nil
}

11
pkg/convert/uint.go Normal file

@ -0,0 +1,11 @@
package convert
import (
"math"
"golang.org/x/exp/constraints"
)
func FloatToUint64[T constraints.Float](number T) uint64 {
return uint64(math.Floor(float64(number)))
}

22
pkg/convert/uint_test.go Normal file

@ -0,0 +1,22 @@
package convert_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert"
)
func TestFloatToUint64(t *testing.T) {
t.Run("Успешная конвертация float32 в uint32", func(t *testing.T) {
assert.Equal(t, uint64(10), convert.FloatToUint64(float32(10.6)))
})
t.Run("Успешная конвертация float64 в uint32", func(t *testing.T) {
assert.Equal(t, uint64(10), convert.FloatToUint64(float64(10.6)))
})
t.Run("Успешная конвертация float64 в uint32 (2)", func(t *testing.T) {
assert.Equal(t, uint64(10), convert.FloatToUint64(float64(10)))
})
}

@ -1,9 +1,9 @@
package defaults
func GetDefaultValue[T any](value *T, defaultValue T) T {
if value == nil {
return defaultValue
}
return *value
}
package defaults
func GetDefaultValue[T any](value *T, defaultValue T) T {
if value == nil {
return defaultValue
}
return *value
}

@ -1,25 +1,24 @@
package defaults_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/defaults"
)
func TestGetDefaultValue(t *testing.T) {
t.Run("Стандартное значение nil", func(t *testing.T) {
assert.Equal(t, 0, defaults.GetDefaultValue(nil, 0))
})
t.Run("Стандартное значение float64", func(t *testing.T) {
assert.Equal(t, float64(0), defaults.GetDefaultValue(nil, float64(0)))
})
t.Run("Стандартное значение строки", func(t *testing.T) {
testString := "test"
assert.Equal(t, "test", defaults.GetDefaultValue(&testString, ""))
})
}
package defaults_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/defaults"
)
func TestGetDefaultValue(t *testing.T) {
t.Run("Стандартное значение nil", func(t *testing.T) {
assert.Equal(t, 0, defaults.GetDefaultValue(nil, 0))
})
t.Run("Стандартное значение float64", func(t *testing.T) {
assert.Equal(t, float64(0), defaults.GetDefaultValue(nil, float64(0)))
})
t.Run("Стандартное значение строки", func(t *testing.T) {
testString := "test"
assert.Equal(t, "test", defaults.GetDefaultValue(&testString, ""))
})
}

2
pkg/env/parse.go vendored

@ -9,7 +9,7 @@ import (
envParser "github.com/sethvargo/go-envconfig"
)
// Parsing default env file or env context and returns struct pointer by generic
// Parse parsing default env file or env context and returns struct pointer by generic.
func Parse[T interface{}](envFilePath string) (*T, error) {
var configuration T

@ -1,22 +1,22 @@
package mongo
import (
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Configuration struct {
Host string `env:"MONGO_HOST,default=localhost"`
Port string `env:"MONGO_PORT,default=27017"`
User string `env:"MONGO_USER,required"`
Password string `env:"MONGO_PASSWORD,required"`
Auth string `env:"MONGO_AUTH,required"`
DatabaseName string `env:"MONGO_DB_NAME,required"`
}
type RequestSettings struct {
Driver *mongo.Collection
Options *options.FindOptions
Filter primitive.M
}
package mongo
import (
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Configuration struct {
Host string `env:"MONGO_HOST,default=localhost"`
Port string `env:"MONGO_PORT,default=27017"`
User string `env:"MONGO_USER,required"`
Password string `env:"MONGO_PASSWORD,required"`
Auth string `env:"MONGO_AUTH,required"`
DatabaseName string `env:"MONGO_DB_NAME,required"`
}
type RequestSettings struct {
Driver *mongo.Collection
Options *options.FindOptions
Filter primitive.M
}

@ -1,71 +1,73 @@
package mongo
import (
"context"
"fmt"
"log"
"net"
"net/url"
"time"
"go.mongodb.org/mongo-driver/event"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type ConnectDeps struct {
Configuration *Configuration
Timeout time.Duration
}
func Connect(ctx context.Context, deps *ConnectDeps) (*mongo.Database, error) {
if deps == nil {
return nil, ErrEmptyArgs
}
mongoURI := &url.URL{
Scheme: "mongodb",
Host: net.JoinHostPort(deps.Configuration.Host, deps.Configuration.Port),
}
cmdMonitor := &event.CommandMonitor{
Started: func(_ context.Context, evt *event.CommandStartedEvent) {
log.Println(evt.Command)
},
Succeeded: func(_ context.Context, evt *event.CommandSucceededEvent) {
log.Println(evt.Reply)
},
Failed: func(_ context.Context, evt *event.CommandFailedEvent) {
log.Println(evt.Failure)
},
}
connectionOptions := options.Client().
ApplyURI(mongoURI.String()).
SetAuth(options.Credential{
AuthMechanism: "SCRAM-SHA-1",
AuthSource: deps.Configuration.Auth,
Username: deps.Configuration.User,
Password: deps.Configuration.Password,
}).
SetMonitor(cmdMonitor)
ticker := time.NewTicker(1 * time.Second)
timeoutExceeded := time.After(deps.Timeout)
defer ticker.Stop()
for {
select {
case <-ticker.C:
connection, err := mongo.Connect(ctx, connectionOptions)
if err == nil {
return connection.Database(deps.Configuration.DatabaseName), nil
}
log.Printf("failed to connect to db <%s>: %s", mongoURI.String(), err.Error())
case <-timeoutExceeded:
return nil, fmt.Errorf("db connection <%s> failed after %d timeout", mongoURI, deps.Timeout)
}
}
}
package mongo
import (
"context"
"fmt"
"log"
"net"
"net/url"
"time"
"go.mongodb.org/mongo-driver/event"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type ConnectDeps struct {
Configuration *Configuration
Timeout time.Duration
}
func Connect(ctx context.Context, deps *ConnectDeps) (*mongo.Database, error) {
if deps == nil {
return nil, ErrEmptyArgs
}
mongoURI := &url.URL{
Scheme: "mongodb",
Host: net.JoinHostPort(deps.Configuration.Host, deps.Configuration.Port),
}
cmdMonitor := &event.CommandMonitor{
Started: func(_ context.Context, evt *event.CommandStartedEvent) {
log.Println(evt.Command)
},
Succeeded: func(_ context.Context, evt *event.CommandSucceededEvent) {
log.Println(evt.Reply)
},
Failed: func(_ context.Context, evt *event.CommandFailedEvent) {
log.Println(evt.Failure)
},
}
connectionOptions := options.Client().
ApplyURI(mongoURI.String()).
SetAuth(options.Credential{
AuthMechanism: "SCRAM-SHA-1",
AuthSource: deps.Configuration.Auth,
Username: deps.Configuration.User,
Password: deps.Configuration.Password,
}).
SetMonitor(cmdMonitor)
ticker := time.NewTicker(1 * time.Second)
timeoutExceeded := time.After(deps.Timeout)
defer ticker.Stop()
for {
select {
case <-ticker.C:
connection, err := mongo.Connect(ctx, connectionOptions)
if err == nil {
return connection.Database(deps.Configuration.DatabaseName), nil
}
log.Printf("failed to connect to db <%s>: %s", mongoURI.String(), err.Error())
case <-timeoutExceeded:
return nil, fmt.Errorf("db connection <%s> failed after %d timeout", mongoURI.String(), deps.Timeout)
default:
time.Sleep(1 * time.Second)
}
}
}

@ -1,7 +1,7 @@
package mongo
import "errors"
var (
ErrEmptyArgs = errors.New("arguments are empty")
)
package mongo
import "errors"
var (
ErrEmptyArgs = errors.New("arguments are empty")
)

@ -1,52 +1,55 @@
package mongo
import (
"context"
)
func Find[T any](ctx context.Context, settings *RequestSettings) ([]T, error) {
if settings == nil {
return []T{}, ErrEmptyArgs
}
results := make([]T, 0)
cursor, err := settings.Driver.Find(ctx, settings.Filter)
if err != nil {
return []T{}, err
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
result := new(T)
if err := cursor.Decode(result); err != nil {
return []T{}, err
}
results = append(results, *result)
}
if err := cursor.Err(); err != nil {
return []T{}, err
}
return results, nil
}
func FindOne[T any](ctx context.Context, settings *RequestSettings) (*T, error) {
if settings == nil {
return nil, ErrEmptyArgs
}
result := new(T)
if err := settings.Driver.FindOne(ctx, settings.Filter).Decode(result); err != nil {
return nil, err
}
return result, nil
}
package mongo
import (
"context"
"log"
)
func Find[T any](ctx context.Context, settings *RequestSettings) ([]T, error) {
if settings == nil {
return []T{}, ErrEmptyArgs
}
results := make([]T, 0)
cursor, err := settings.Driver.Find(ctx, settings.Filter, settings.Options)
if err != nil {
return []T{}, err
}
defer func() {
if err := cursor.Close(ctx); err != nil {
log.Printf("failed to close cursor: %v", err)
}
}()
for cursor.Next(ctx) {
result := new(T)
if err := cursor.Decode(result); err != nil {
return []T{}, err
}
results = append(results, *result)
}
if err := cursor.Err(); err != nil {
return []T{}, err
}
return results, nil
}
func FindOne[T any](ctx context.Context, settings *RequestSettings) (*T, error) {
if settings == nil {
return nil, ErrEmptyArgs
}
result := new(T)
if err := settings.Driver.FindOne(ctx, settings.Filter).Decode(result); err != nil {
return nil, err
}
return result, nil
}

@ -1,35 +1,35 @@
package utils
import (
"reflect"
)
func GetFilledFieldsCount(object interface{}) int {
filledFieldsCount := int(0)
if object == nil {
return 0
}
value := reflect.ValueOf(object)
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
fieldsCount := value.NumField()
if fieldsCount < 1 {
return 0
}
for index := 0; index < fieldsCount; index++ {
field := value.Field(index)
if field.IsZero() {
continue
}
filledFieldsCount++
}
return filledFieldsCount
}
package utils
import (
"reflect"
)
func GetFilledFieldsCount(object interface{}) int {
filledFieldsCount := int(0)
if object == nil {
return 0
}
value := reflect.ValueOf(object)
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
fieldsCount := value.NumField()
if fieldsCount < 1 {
return 0
}
for index := 0; index < fieldsCount; index++ {
field := value.Field(index)
if field.IsZero() {
continue
}
filledFieldsCount++
}
return filledFieldsCount
}

@ -1,65 +1,64 @@
package utils_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/utils"
)
func TestGetFilledFieldsCount(t *testing.T) {
name := "name"
age := uint(10)
t.Run("Получение количества заполненных полей структуры с пустой структурой", func(t *testing.T) {
assert.Equal(t, 0, utils.GetFilledFieldsCount(struct{}{}))
})
t.Run("Получение количества заполненных полей структуры с nil", func(t *testing.T) {
assert.Equal(t, 0, utils.GetFilledFieldsCount(nil))
})
t.Run("Получение количества заполненных полей структуры с указателем на пустую структуру", func(t *testing.T) {
assert.Equal(t, 0, utils.GetFilledFieldsCount(&struct{}{}))
})
t.Run("Получение количества заполненных полей структуры с заполненной структурой", func(t *testing.T) {
assert.Equal(t, 1, utils.GetFilledFieldsCount(struct {
name *string
age *uint
}{
name: &name,
}))
})
t.Run("Получение количества заполненных полей структуры с указателем на заполненную структурой", func(t *testing.T) {
assert.Equal(t, 2, utils.GetFilledFieldsCount(&struct {
name *string
age *uint
}{
name: &name,
age: &age,
}))
})
t.Run("Получение количества заполненных полей структуры с заполненную структурой не опциональных значений", func(t *testing.T) {
assert.Equal(t, 2, utils.GetFilledFieldsCount(&struct {
name *string
age uint
}{
name: &name,
age: age,
}))
})
t.Run("Получение количества заполненных полей структуры с не заполненную структурой не опциональных значений", func(t *testing.T) {
assert.Equal(t, 0, utils.GetFilledFieldsCount(&struct {
name string
age uint
}{
name: "",
age: 0,
}))
})
}
package utils_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/utils"
)
func TestGetFilledFieldsCount(t *testing.T) {
name := "name"
age := uint(10)
t.Run("Получение количества заполненных полей структуры с пустой структурой", func(t *testing.T) {
assert.Equal(t, 0, utils.GetFilledFieldsCount(struct{}{}))
})
t.Run("Получение количества заполненных полей структуры с nil", func(t *testing.T) {
assert.Equal(t, 0, utils.GetFilledFieldsCount(nil))
})
t.Run("Получение количества заполненных полей структуры с указателем на пустую структуру", func(t *testing.T) {
assert.Equal(t, 0, utils.GetFilledFieldsCount(&struct{}{}))
})
t.Run("Получение количества заполненных полей структуры с заполненной структурой", func(t *testing.T) {
assert.Equal(t, 1, utils.GetFilledFieldsCount(struct {
name *string
age *uint
}{
name: &name,
}))
})
t.Run("Получение количества заполненных полей структуры с указателем на заполненную структурой", func(t *testing.T) {
assert.Equal(t, 2, utils.GetFilledFieldsCount(&struct {
name *string
age *uint
}{
name: &name,
age: &age,
}))
})
t.Run("Получение количества заполненных полей структуры с заполненную структурой не опциональных значений", func(t *testing.T) {
assert.Equal(t, 2, utils.GetFilledFieldsCount(&struct {
name *string
age uint
}{
name: &name,
age: age,
}))
})
t.Run("Получение количества заполненных полей структуры с не заполненную структурой не опциональных значений", func(t *testing.T) {
assert.Equal(t, 0, utils.GetFilledFieldsCount(&struct {
name string
age uint
}{
name: "",
age: 0,
}))
})
}

@ -1,50 +1,50 @@
package utils_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/utils"
)
func TestMergeMaps(t *testing.T) {
t.Run("Соединение двух пустых map", func(t *testing.T) {
assert.Equal(t, map[string]interface{}{}, utils.MergeMaps(map[string]interface{}{}, map[string]interface{}{}))
})
t.Run("Соединение несколько пустых map", func(t *testing.T) {
assert.Equal(t, map[string]interface{}{}, utils.MergeMaps(map[string]interface{}{}, map[string]interface{}{}, map[string]interface{}{}))
})
t.Run("Соединение заполненных map", func(t *testing.T) {
assert.Equal(t,
map[string]interface{}{
"test1": "1",
"test2": "1",
"test3": "1",
"test4": "1",
},
utils.MergeMaps(
map[string]interface{}{"test1": "1"},
map[string]interface{}{"test2": "1"},
map[string]interface{}{"test3": "1", "test4": "1"},
),
)
})
t.Run("Соединение заполненных map с одинаковыми ключами", func(t *testing.T) {
assert.Equal(t,
map[string]interface{}{
"test1": "1",
"test2": "1",
"test3": "1",
"test4": "1",
},
utils.MergeMaps(
map[string]interface{}{"test1": "1"},
map[string]interface{}{"test2": "1", "test1": "1"},
map[string]interface{}{"test3": "1", "test4": "1"},
),
)
})
}
package utils_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/utils"
)
func TestMergeMaps(t *testing.T) {
t.Run("Соединение двух пустых map", func(t *testing.T) {
assert.Equal(t, map[string]interface{}{}, utils.MergeMaps(map[string]interface{}{}, map[string]interface{}{}))
})
t.Run("Соединение несколько пустых map", func(t *testing.T) {
assert.Equal(t, map[string]interface{}{}, utils.MergeMaps(map[string]interface{}{}, map[string]interface{}{}, map[string]interface{}{}))
})
t.Run("Соединение заполненных map", func(t *testing.T) {
assert.Equal(t,
map[string]interface{}{
"test1": "1",
"test2": "1",
"test3": "1",
"test4": "1",
},
utils.MergeMaps(
map[string]interface{}{"test1": "1"},
map[string]interface{}{"test2": "1"},
map[string]interface{}{"test3": "1", "test4": "1"},
),
)
})
t.Run("Соединение заполненных map с одинаковыми ключами", func(t *testing.T) {
assert.Equal(t,
map[string]interface{}{
"test1": "1",
"test2": "1",
"test3": "1",
"test4": "1",
},
utils.MergeMaps(
map[string]interface{}{"test1": "1"},
map[string]interface{}{"test2": "1", "test1": "1"},
map[string]interface{}{"test3": "1", "test4": "1"},
),
)
})
}

@ -1,17 +1,17 @@
syntax = "proto3";
package discount;
import "google/api/annotations.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
option go_package = "./discount";
message Audit {
google.protobuf.Timestamp UpdatedAt = 1;
google.protobuf.Timestamp CreatedAt = 2;
optional google.protobuf.Timestamp DeletedAt = 3;
bool Deleted = 4;
}
syntax = "proto3";
package discount;
import "google/api/annotations.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
option go_package = "./discount";
message Audit {
google.protobuf.Timestamp UpdatedAt = 1;
google.protobuf.Timestamp CreatedAt = 2;
optional google.protobuf.Timestamp DeletedAt = 3;
bool Deleted = 4;
}

Some files were not shown because too many files have changed in this diff Show More