+ categories sort + analytics
This commit is contained in:
@@ -38,10 +38,11 @@ dependencies {
|
|||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-validation")
|
implementation("org.springframework.boot:spring-boot-starter-validation")
|
||||||
implementation("commons-logging:commons-logging:1.3.4")
|
implementation("commons-logging:commons-logging:1.3.4")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
|
||||||
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-cache")
|
implementation("org.springframework.boot:spring-boot-starter-cache")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||||
|
implementation ("org.springframework.boot:spring-boot-starter-actuator")
|
||||||
|
|
||||||
|
|
||||||
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
|
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
|
||||||
implementation("io.jsonwebtoken:jjwt-impl:0.11.5")
|
implementation("io.jsonwebtoken:jjwt-impl:0.11.5")
|
||||||
@@ -53,10 +54,11 @@ dependencies {
|
|||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.7.3")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.7.3")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
|
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
|
||||||
implementation("com.google.code.gson:gson")
|
implementation("com.google.code.gson:gson")
|
||||||
|
implementation("io.micrometer:micrometer-registry-prometheus")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
runtimeOnly("org.postgresql:postgresql")
|
|
||||||
compileOnly("org.projectlombok:lombok")
|
compileOnly("org.projectlombok:lombok")
|
||||||
annotationProcessor("org.projectlombok:lombok")
|
annotationProcessor("org.projectlombok:lombok")
|
||||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||||
@@ -73,3 +75,5 @@ kotlin {
|
|||||||
tasks.withType<Test> {
|
tasks.withType<Test> {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ class BearerTokenFilter(private val authService: AuthService) : SecurityContextS
|
|||||||
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
|
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
|
||||||
val token = exchange.request.headers.getFirst(HttpHeaders.AUTHORIZATION)?.removePrefix("Bearer ")
|
val token = exchange.request.headers.getFirst(HttpHeaders.AUTHORIZATION)?.removePrefix("Bearer ")
|
||||||
|
|
||||||
if (exchange.request.path.value() == "/api/auth/login"){
|
logger.info("here ${exchange.request.path.value()}")
|
||||||
|
if (exchange.request.path.value() == "/api/auth/login" || exchange.request.path.value()
|
||||||
|
.startsWith("/api/actuator")
|
||||||
|
) {
|
||||||
return chain.filter(exchange)
|
return chain.filter(exchange)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,8 +55,6 @@ class BearerTokenFilter(private val authService: AuthService) : SecurityContextS
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package space.luminic.budgerapp.configs
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
|
||||||
|
//@Configuration
|
||||||
|
//class CommonConfig {
|
||||||
|
// @Bean
|
||||||
|
// fun httpTraceRepository(): HttpTraceRepository {
|
||||||
|
// return InMemoryHttpTraceRepository()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
@@ -35,7 +35,7 @@ class BudgetController(
|
|||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
fun getBudget(@PathVariable id: String): Mono<BudgetDTO> {
|
fun getBudget(@PathVariable id: String): Mono<BudgetDTO> {
|
||||||
|
logger.info("here }")
|
||||||
return budgetService.getBudget(id)
|
return budgetService.getBudget(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package space.luminic.budgerapp.controllers
|
package space.luminic.budgerapp.controllers
|
||||||
|
|
||||||
|
import org.bson.Document
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
@@ -10,25 +11,33 @@ import org.springframework.web.bind.annotation.PostMapping
|
|||||||
import org.springframework.web.bind.annotation.PutMapping
|
import org.springframework.web.bind.annotation.PutMapping
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
import org.springframework.web.client.HttpClientErrorException
|
import org.springframework.web.client.HttpClientErrorException
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
|
import space.luminic.budgerapp.models.BudgetCategory
|
||||||
import space.luminic.budgerapp.models.Category
|
import space.luminic.budgerapp.models.Category
|
||||||
|
import space.luminic.budgerapp.services.BudgetService
|
||||||
import space.luminic.budgerapp.services.CategoryService
|
import space.luminic.budgerapp.services.CategoryService
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/categories")
|
@RequestMapping("/categories")
|
||||||
class CategoriesController(
|
class CategoriesController(
|
||||||
private val categoryService: CategoryService
|
private val categoryService: CategoryService,
|
||||||
|
private val budgetService: BudgetService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(javaClass)
|
private val logger = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
@GetMapping()
|
@GetMapping()
|
||||||
fun getCategories(): Mono<List<Category>> {
|
fun getCategories(
|
||||||
return categoryService.getCategories()
|
@RequestParam("sort") sortBy: String = "name",
|
||||||
|
@RequestParam("direction") direction: String = "ASC"
|
||||||
|
): Mono<List<Category>> {
|
||||||
|
return categoryService.getCategories(sortBy, direction)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,4 +66,19 @@ class CategoriesController(
|
|||||||
return categoryService.deleteCategory(categoryId)
|
return categoryService.deleteCategory(categoryId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/test")
|
||||||
|
fun test(): Mono<MutableList<BudgetCategory>> {
|
||||||
|
var dateFrom = LocalDate.parse("2025-01-10")
|
||||||
|
var dateTo = LocalDate.parse("2025-02-09")
|
||||||
|
|
||||||
|
return categoryService.getCategoryTransactionPipeline(dateFrom, dateTo)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/by-month")
|
||||||
|
fun getCategoriesSumsByMonths(): Mono<List<Document>> {
|
||||||
|
return categoryService.getCategorySumsPipeline(LocalDate.of(2024, 8, 1), LocalDate.of(2025, 1, 12))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,9 @@ package space.luminic.budgerapp.models
|
|||||||
import org.springframework.data.annotation.Id
|
import org.springframework.data.annotation.Id
|
||||||
import org.springframework.data.mongodb.core.mapping.DBRef
|
import org.springframework.data.mongodb.core.mapping.DBRef
|
||||||
import org.springframework.data.mongodb.core.mapping.Document
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
import java.math.BigDecimal
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
import kotlin.collections.mutableListOf
|
||||||
|
|
||||||
|
|
||||||
data class BudgetDTO(
|
data class BudgetDTO(
|
||||||
@@ -19,7 +19,7 @@ data class BudgetDTO(
|
|||||||
var categories: MutableList<BudgetCategory> = mutableListOf(),
|
var categories: MutableList<BudgetCategory> = mutableListOf(),
|
||||||
var transactions: MutableList<Transaction>? = null,
|
var transactions: MutableList<Transaction>? = null,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@Document("budgets")
|
@Document("budgets")
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ interface BudgetRepo: ReactiveMongoRepository<Budget, String> {
|
|||||||
|
|
||||||
override fun findAll(sort: Sort): Flux<Budget>
|
override fun findAll(sort: Sort): Flux<Budget>
|
||||||
|
|
||||||
fun findByDateFromLessThanEqualAndDateToGreaterThan(dateOne: LocalDate, dateTwo: LocalDate): Mono<Budget>
|
fun findByDateFromLessThanEqualAndDateToGreaterThanEqual(dateOne: LocalDate, dateTwo: LocalDate): Mono<Budget>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -10,4 +10,6 @@ interface CategoryRepo : ReactiveMongoRepository<Category, String> {
|
|||||||
|
|
||||||
|
|
||||||
fun findByName(name: String): Mono<Category>
|
fun findByName(name: String): Mono<Category>
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package space.luminic.budgerapp.services
|
package space.luminic.budgerapp.services
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.cache.annotation.CacheEvict
|
import org.springframework.cache.annotation.CacheEvict
|
||||||
import org.springframework.cache.annotation.Cacheable
|
import org.springframework.cache.annotation.Cacheable
|
||||||
@@ -59,27 +60,32 @@ class BudgetService(
|
|||||||
TransactionEventType.DELETE -> updateBudgetOnDelete(event)
|
TransactionEventType.DELETE -> updateBudgetOnDelete(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// runBlocking(Dispatchers.IO) {
|
// runBlocking(Dispatchers.IO) {
|
||||||
// updateBudgetWarns(
|
// updateBudgetWarns(
|
||||||
// budget = budgetRepo.findByDateFromLessThanEqualAndDateToGreaterThan(
|
// budgetRepo.findByDateFromLessThanEqualAndDateToGreaterThan(
|
||||||
// event.newTransaction.date.toLocalDate(), event.newTransaction.date.toLocalDate()
|
// event.newTransaction.date, event.newTransaction.date.
|
||||||
// )
|
// ).map{it}
|
||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateBudgetOnCreate(event: TransactionEvent) {
|
fun updateBudgetOnCreate(event: TransactionEvent) {
|
||||||
budgetRepo.findByDateFromLessThanEqualAndDateToGreaterThan(
|
budgetRepo.findByDateFromLessThanEqualAndDateToGreaterThanEqual(
|
||||||
event.newTransaction.date, event.newTransaction.date
|
event.newTransaction.date, event.newTransaction.date
|
||||||
).flatMap { budget ->
|
).flatMap { budget ->
|
||||||
categoryService.getBudgetCategories(budget.dateFrom, budget.dateTo).flatMap { categories ->
|
val categories = categoryService.getBudgetCategories(budget.dateFrom, budget.dateTo)
|
||||||
|
logger.info(categories.toString())
|
||||||
|
categories.flatMap { categories ->
|
||||||
val updatedCategories = when (event.newTransaction.type.code) {
|
val updatedCategories = when (event.newTransaction.type.code) {
|
||||||
"PLANNED" -> Flux.fromIterable(budget.categories)
|
"PLANNED" -> Flux.fromIterable(budget.categories)
|
||||||
.map { category ->
|
.map { category ->
|
||||||
categories[category.category.id]?.let { data ->
|
categories[category.category.id]?.let { data ->
|
||||||
category.currentSpent = data["instantAmount"] ?: 0.0
|
if (category.category.id == event.newTransaction.category.id) {
|
||||||
category.currentPlanned = data["plannedAmount"] ?: 0.0
|
category.currentSpent = data["instantAmount"] ?: 0.0
|
||||||
category.currentLimit += event.newTransaction.amount
|
category.currentPlanned = data["plannedAmount"] ?: 0.0
|
||||||
|
category.currentLimit += event.newTransaction.amount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
category
|
category
|
||||||
}.collectList()
|
}.collectList()
|
||||||
@@ -87,8 +93,10 @@ class BudgetService(
|
|||||||
"INSTANT" -> Flux.fromIterable(budget.categories)
|
"INSTANT" -> Flux.fromIterable(budget.categories)
|
||||||
.map { category ->
|
.map { category ->
|
||||||
categories[category.category.id]?.let { data ->
|
categories[category.category.id]?.let { data ->
|
||||||
category.currentSpent = data["instantAmount"] ?: 0.0
|
if (category.category.id == event.newTransaction.category.id) {
|
||||||
category.currentPlanned = data["plannedAmount"] ?: 0.0
|
category.currentSpent = data["instantAmount"] ?: 0.0
|
||||||
|
category.currentPlanned = data["plannedAmount"] ?: 0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
category
|
category
|
||||||
}.collectList()
|
}.collectList()
|
||||||
@@ -107,13 +115,13 @@ class BudgetService(
|
|||||||
|
|
||||||
|
|
||||||
fun updateBudgetOnEdit(event: TransactionEvent) {
|
fun updateBudgetOnEdit(event: TransactionEvent) {
|
||||||
budgetRepo.findByDateFromLessThanEqualAndDateToGreaterThan(
|
budgetRepo.findByDateFromLessThanEqualAndDateToGreaterThanEqual(
|
||||||
event.oldTransaction.date, event.oldTransaction.date
|
event.oldTransaction.date, event.oldTransaction.date
|
||||||
).switchIfEmpty(
|
).switchIfEmpty(
|
||||||
Mono.error(BudgetNotFoundException("old budget cannot be null"))
|
Mono.error(BudgetNotFoundException("old budget cannot be null"))
|
||||||
).then().subscribe()
|
).then().subscribe()
|
||||||
|
|
||||||
budgetRepo.findByDateFromLessThanEqualAndDateToGreaterThan(
|
budgetRepo.findByDateFromLessThanEqualAndDateToGreaterThanEqual(
|
||||||
event.newTransaction.date, event.newTransaction.date
|
event.newTransaction.date, event.newTransaction.date
|
||||||
).flatMap { budget ->
|
).flatMap { budget ->
|
||||||
categoryService.getBudgetCategories(budget.dateFrom, budget.dateTo).flatMap { categories ->
|
categoryService.getBudgetCategories(budget.dateFrom, budget.dateTo).flatMap { categories ->
|
||||||
@@ -134,8 +142,10 @@ class BudgetService(
|
|||||||
"INSTANT" -> Flux.fromIterable(budget.categories)
|
"INSTANT" -> Flux.fromIterable(budget.categories)
|
||||||
.map { category ->
|
.map { category ->
|
||||||
categories[category.category.id]?.let { data ->
|
categories[category.category.id]?.let { data ->
|
||||||
category.currentSpent = data["instantAmount"] ?: 0.0
|
if (category.category.id == event.newTransaction.category.id) {
|
||||||
category.currentPlanned = data["plannedAmount"] ?: 0.0
|
category.currentSpent = data["instantAmount"] ?: 0.0
|
||||||
|
category.currentPlanned = data["plannedAmount"] ?: 0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
category
|
category
|
||||||
}.collectList()
|
}.collectList()
|
||||||
@@ -154,7 +164,7 @@ class BudgetService(
|
|||||||
|
|
||||||
|
|
||||||
fun updateBudgetOnDelete(event: TransactionEvent) {
|
fun updateBudgetOnDelete(event: TransactionEvent) {
|
||||||
budgetRepo.findByDateFromLessThanEqualAndDateToGreaterThan(
|
budgetRepo.findByDateFromLessThanEqualAndDateToGreaterThanEqual(
|
||||||
event.newTransaction.date, event.newTransaction.date
|
event.newTransaction.date, event.newTransaction.date
|
||||||
).flatMap { budget ->
|
).flatMap { budget ->
|
||||||
categoryService.getBudgetCategories(budget.dateFrom, budget.dateTo).flatMap { categories ->
|
categoryService.getBudgetCategories(budget.dateFrom, budget.dateTo).flatMap { categories ->
|
||||||
@@ -162,9 +172,11 @@ class BudgetService(
|
|||||||
"PLANNED" -> Flux.fromIterable(budget.categories)
|
"PLANNED" -> Flux.fromIterable(budget.categories)
|
||||||
.map { category ->
|
.map { category ->
|
||||||
categories[category.category.id]?.let { data ->
|
categories[category.category.id]?.let { data ->
|
||||||
category.currentSpent = data["instantAmount"] ?: 0.0
|
if (category.category.id == event.newTransaction.category.id) {
|
||||||
category.currentPlanned = data["plannedAmount"] ?: 0.0
|
category.currentSpent = data["instantAmount"] ?: 0.0
|
||||||
category.currentLimit += event.newTransaction.amount
|
category.currentPlanned = data["plannedAmount"] ?: 0.0
|
||||||
|
category.currentLimit += event.newTransaction.amount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
category
|
category
|
||||||
}.collectList()
|
}.collectList()
|
||||||
@@ -172,8 +184,10 @@ class BudgetService(
|
|||||||
"INSTANT" -> Flux.fromIterable(budget.categories)
|
"INSTANT" -> Flux.fromIterable(budget.categories)
|
||||||
.map { category ->
|
.map { category ->
|
||||||
categories[category.category.id]?.let { data ->
|
categories[category.category.id]?.let { data ->
|
||||||
category.currentSpent = data["instantAmount"] ?: 0.0
|
if (category.category.id == event.newTransaction.category.id) {
|
||||||
category.currentPlanned = data["plannedAmount"] ?: 0.0
|
category.currentSpent = data["instantAmount"] ?: 0.0
|
||||||
|
category.currentPlanned = data["plannedAmount"] ?: 0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
category
|
category
|
||||||
}.collectList()
|
}.collectList()
|
||||||
@@ -206,6 +220,7 @@ class BudgetService(
|
|||||||
|
|
||||||
// @Cacheable("budgets", key = "#id")
|
// @Cacheable("budgets", key = "#id")
|
||||||
fun getBudget(id: String): Mono<BudgetDTO> {
|
fun getBudget(id: String): Mono<BudgetDTO> {
|
||||||
|
logger.info("here b")
|
||||||
return budgetRepo.findById(id)
|
return budgetRepo.findById(id)
|
||||||
.flatMap { budget ->
|
.flatMap { budget ->
|
||||||
val budgetDTO = BudgetDTO(
|
val budgetDTO = BudgetDTO(
|
||||||
@@ -243,6 +258,7 @@ class BudgetService(
|
|||||||
budgetDTO.plannedExpenses = transactions["plannedExpenses"] as MutableList
|
budgetDTO.plannedExpenses = transactions["plannedExpenses"] as MutableList
|
||||||
budgetDTO.plannedIncomes = transactions["plannedIncomes"] as MutableList
|
budgetDTO.plannedIncomes = transactions["plannedIncomes"] as MutableList
|
||||||
budgetDTO.transactions = transactions["instantTransactions"] as MutableList
|
budgetDTO.transactions = transactions["instantTransactions"] as MutableList
|
||||||
|
logger.info("here e")
|
||||||
|
|
||||||
budgetDTO
|
budgetDTO
|
||||||
}
|
}
|
||||||
@@ -301,7 +317,7 @@ class BudgetService(
|
|||||||
|
|
||||||
|
|
||||||
fun getBudgetByDate(date: LocalDate): Mono<Budget> {
|
fun getBudgetByDate(date: LocalDate): Mono<Budget> {
|
||||||
return budgetRepo.findByDateFromLessThanEqualAndDateToGreaterThan(date, date).switchIfEmpty(Mono.empty())
|
return budgetRepo.findByDateFromLessThanEqualAndDateToGreaterThanEqual(date, date).switchIfEmpty(Mono.empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
// fun getBudgetCategorySQL(id: Int): List<BudgetCategory>? {
|
// fun getBudgetCategorySQL(id: Int): List<BudgetCategory>? {
|
||||||
@@ -360,7 +376,7 @@ class BudgetService(
|
|||||||
@CacheEvict(cacheNames = ["budgets", "budgetsList"], allEntries = true)
|
@CacheEvict(cacheNames = ["budgets", "budgetsList"], allEntries = true)
|
||||||
fun setCategoryLimit(budgetId: String, catId: String, limit: Double): Mono<BudgetCategory> {
|
fun setCategoryLimit(budgetId: String, catId: String, limit: Double): Mono<BudgetCategory> {
|
||||||
return budgetRepo.findById(budgetId).flatMap { budget ->
|
return budgetRepo.findById(budgetId).flatMap { budget ->
|
||||||
val catEdit = budget.categories.firstOrNull { it.category.id == catId }
|
val catEdit = budget.categories.firstOrNull { it.category?.id == catId }
|
||||||
?: return@flatMap Mono.error<BudgetCategory>(Exception("Category not found in the budget"))
|
?: return@flatMap Mono.error<BudgetCategory>(Exception("Category not found in the budget"))
|
||||||
|
|
||||||
transactionService.calcTransactionsSum(budget, catId, "PLANNED").flatMap { catPlanned ->
|
transactionService.calcTransactionsSum(budget, catId, "PLANNED").flatMap { catPlanned ->
|
||||||
@@ -378,9 +394,6 @@ class BudgetService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun getWarns(budgetId: String, isHide: Boolean? = null): Mono<List<Warn>> {
|
fun getWarns(budgetId: String, isHide: Boolean? = null): Mono<List<Warn>> {
|
||||||
return warnRepo.findAllByBudgetIdAndIsHide(budgetId, isHide == true).collectList()
|
return warnRepo.findAllByBudgetIdAndIsHide(budgetId, isHide == true).collectList()
|
||||||
}
|
}
|
||||||
@@ -469,10 +482,10 @@ class BudgetService(
|
|||||||
): Flux<Warn> {
|
): Flux<Warn> {
|
||||||
val warnsForCategory = mutableListOf<Mono<Warn?>>()
|
val warnsForCategory = mutableListOf<Mono<Warn?>>()
|
||||||
|
|
||||||
val averageSum = averageSums[category.category.id] ?: 0.0
|
val averageSum = averageSums[category.category?.id] ?: 0.0
|
||||||
val categorySpentRatioInAvgIncome = if (averageIncome > 0.0) averageSum / averageIncome else 0.0
|
val categorySpentRatioInAvgIncome = if (averageIncome > 0.0) averageSum / averageIncome else 0.0
|
||||||
val projectedAvailableSum = currentBudgetIncome * categorySpentRatioInAvgIncome
|
val projectedAvailableSum = currentBudgetIncome * categorySpentRatioInAvgIncome
|
||||||
val contextAtAvg = "category${category.category.id}atbudget${finalBudget.id}lessavg"
|
val contextAtAvg = "category${category.category?.id}atbudget${finalBudget.id}lessavg"
|
||||||
val lowSavingContext = "savingValueLess10atBudget${finalBudget.id}"
|
val lowSavingContext = "savingValueLess10atBudget${finalBudget.id}"
|
||||||
|
|
||||||
if (averageSum > category.currentLimit) {
|
if (averageSum > category.currentLimit) {
|
||||||
@@ -482,11 +495,11 @@ class BudgetService(
|
|||||||
Warn(
|
Warn(
|
||||||
serenity = WarnSerenity.MAIN,
|
serenity = WarnSerenity.MAIN,
|
||||||
message = PushMessage(
|
message = PushMessage(
|
||||||
title = "Внимание на ${category.category.name}!",
|
title = "Внимание на ${category.category?.name}!",
|
||||||
body = "Лимит меньше средних трат (Среднее: <b>${averageSum.toInt()} ₽</b> Текущий лимит: <b>${category.currentLimit.toInt()} ₽</b>)." +
|
body = "Лимит меньше средних трат (Среднее: <b>${averageSum.toInt()} ₽</b> Текущий лимит: <b>${category.currentLimit.toInt()} ₽</b>)." +
|
||||||
"\nСредняя доля данной категории в доходах: <b>${(categorySpentRatioInAvgIncome * 100).toInt()}%</b>." +
|
"\nСредняя доля данной категории в доходах: <b>${(categorySpentRatioInAvgIncome * 100).toInt()}%</b>." +
|
||||||
"\nПроецируется на текущие поступления: <b>${projectedAvailableSum.toInt()} ₽</b>",
|
"\nПроецируется на текущие поступления: <b>${projectedAvailableSum.toInt()} ₽</b>",
|
||||||
icon = category.category.icon
|
icon = category.category?.icon
|
||||||
),
|
),
|
||||||
budgetId = finalBudget.id!!,
|
budgetId = finalBudget.id!!,
|
||||||
context = contextAtAvg,
|
context = contextAtAvg,
|
||||||
@@ -499,7 +512,7 @@ class BudgetService(
|
|||||||
warnRepo.findWarnByContext(contextAtAvg).flatMap { warnRepo.delete(it).then(Mono.empty<Warn>()) }
|
warnRepo.findWarnByContext(contextAtAvg).flatMap { warnRepo.delete(it).then(Mono.empty<Warn>()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (category.category.id == "675850148198643f121e466a") {
|
if (category.category?.id == "675850148198643f121e466a") {
|
||||||
val savingRatio = if (plannedIncome > 0.0) category.currentLimit / plannedIncome else 0.0
|
val savingRatio = if (plannedIncome > 0.0) category.currentLimit / plannedIncome else 0.0
|
||||||
if (savingRatio < 0.1) {
|
if (savingRatio < 0.1) {
|
||||||
val warnMono = warnRepo.findWarnByContext(lowSavingContext)
|
val warnMono = warnRepo.findWarnByContext(lowSavingContext)
|
||||||
@@ -512,7 +525,7 @@ class BudgetService(
|
|||||||
body = "Текущие плановые сбережения равны ${plannedSaving.toInt()} (${
|
body = "Текущие плановые сбережения равны ${plannedSaving.toInt()} (${
|
||||||
(savingRatio * 100).toInt()
|
(savingRatio * 100).toInt()
|
||||||
}%)! Исправьте!",
|
}%)! Исправьте!",
|
||||||
icon = category.category.icon
|
icon = category.category!!.icon
|
||||||
),
|
),
|
||||||
budgetId = finalBudget.id!!,
|
budgetId = finalBudget.id!!,
|
||||||
context = lowSavingContext,
|
context = lowSavingContext,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import org.bson.Document
|
|||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.cache.annotation.CacheEvict
|
import org.springframework.cache.annotation.CacheEvict
|
||||||
import org.springframework.cache.annotation.Cacheable
|
import org.springframework.cache.annotation.Cacheable
|
||||||
|
import org.springframework.data.domain.Sort
|
||||||
|
import org.springframework.data.domain.Sort.Direction
|
||||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
@@ -30,9 +32,16 @@ class CategoryService(
|
|||||||
private val logger = LoggerFactory.getLogger(javaClass)
|
private val logger = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
|
|
||||||
|
fun getCategory(id: String): Mono<Category> {
|
||||||
|
return categoryRepo.findById(id)
|
||||||
|
}
|
||||||
|
|
||||||
@Cacheable("getAllCategories")
|
@Cacheable("getAllCategories")
|
||||||
fun getCategories(): Mono<List<Category>> {
|
fun getCategories(sortBy: String, direction: String): Mono<List<Category>> {
|
||||||
return categoryRepo.findAll().collectList()
|
return categoryRepo.findAll(Sort.by(if (direction == "ASC") Direction.ASC else Direction.DESC, sortBy))
|
||||||
|
.collectList()
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cacheable("categoryTypes")
|
@Cacheable("categoryTypes")
|
||||||
@@ -43,12 +52,12 @@ class CategoryService(
|
|||||||
return types
|
return types
|
||||||
}
|
}
|
||||||
|
|
||||||
@CacheEvict(cacheNames = ["getAllCategories"],allEntries = true)
|
@CacheEvict(cacheNames = ["getAllCategories"], allEntries = true)
|
||||||
fun createCategory(category: Category): Mono<Category> {
|
fun createCategory(category: Category): Mono<Category> {
|
||||||
return categoryRepo.save(category)
|
return categoryRepo.save(category)
|
||||||
}
|
}
|
||||||
|
|
||||||
@CacheEvict(cacheNames = ["getAllCategories"],allEntries = true)
|
@CacheEvict(cacheNames = ["getAllCategories"], allEntries = true)
|
||||||
fun editCategory(category: Category): Mono<Category> {
|
fun editCategory(category: Category): Mono<Category> {
|
||||||
return categoryRepo.findById(category.id!!) // Возвращаем Mono<Category>
|
return categoryRepo.findById(category.id!!) // Возвращаем Mono<Category>
|
||||||
.flatMap { oldCategory ->
|
.flatMap { oldCategory ->
|
||||||
@@ -59,7 +68,7 @@ class CategoryService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CacheEvict(cacheNames = ["getAllCategories"],allEntries = true)
|
@CacheEvict(cacheNames = ["getAllCategories"], allEntries = true)
|
||||||
fun deleteCategory(categoryId: String): Mono<String> {
|
fun deleteCategory(categoryId: String): Mono<String> {
|
||||||
return categoryRepo.findById(categoryId).switchIfEmpty(
|
return categoryRepo.findById(categoryId).switchIfEmpty(
|
||||||
Mono.error(IllegalArgumentException("Category with id: $categoryId not found"))
|
Mono.error(IllegalArgumentException("Category with id: $categoryId not found"))
|
||||||
@@ -75,9 +84,9 @@ class CategoryService(
|
|||||||
icon = "🚮"
|
icon = "🚮"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).flatMapMany { newCategory ->
|
).flatMapMany { Category ->
|
||||||
Flux.fromIterable(transactions).flatMap { transaction ->
|
Flux.fromIterable(transactions).flatMap { transaction ->
|
||||||
transaction.category = newCategory // Присваиваем конкретный объект категории
|
transaction.category = Category // Присваиваем конкретный объект категории
|
||||||
transactionService.editTransaction(transaction) // Сохраняем изменения
|
transactionService.editTransaction(transaction) // Сохраняем изменения
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +127,7 @@ class CategoryService(
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
Document(
|
Document(
|
||||||
"\$lt", listOf(
|
"\$lte", listOf(
|
||||||
"\$date",
|
"\$date",
|
||||||
Date.from(
|
Date.from(
|
||||||
LocalDateTime.of(dateTo, LocalTime.MIN)
|
LocalDateTime.of(dateTo, LocalTime.MIN)
|
||||||
@@ -339,7 +348,7 @@ class CategoryService(
|
|||||||
currentPlanned = document["plannedAmount"] as Double,
|
currentPlanned = document["plannedAmount"] as Double,
|
||||||
category = Category(
|
category = Category(
|
||||||
document["_id"].toString(),
|
document["_id"].toString(),
|
||||||
CategoryType(catType["code"] as String, catType["name"] as String),
|
type = CategoryType(catType["code"] as String, catType["name"] as String),
|
||||||
name = document["name"] as String,
|
name = document["name"] as String,
|
||||||
description = document["description"] as String,
|
description = document["description"] as String,
|
||||||
icon = document["icon"] as String
|
icon = document["icon"] as String
|
||||||
@@ -351,4 +360,190 @@ class CategoryService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun getCategorySumsPipeline(dateFrom: LocalDate, dateTo: LocalDate): Mono<List<Document>> {
|
||||||
|
val pipeline = listOf(
|
||||||
|
Document(
|
||||||
|
"\$lookup",
|
||||||
|
Document("from", "categories")
|
||||||
|
.append("localField", "category.\$id")
|
||||||
|
.append("foreignField", "_id")
|
||||||
|
.append("as", "categoryDetails")
|
||||||
|
),
|
||||||
|
Document("\$unwind", "\$categoryDetails"),
|
||||||
|
Document(
|
||||||
|
"\$match",
|
||||||
|
Document(
|
||||||
|
"date",
|
||||||
|
Document(
|
||||||
|
"\$gte", Date.from(
|
||||||
|
LocalDateTime.of(dateTo, LocalTime.MIN)
|
||||||
|
.atZone(ZoneId.systemDefault())
|
||||||
|
.withZoneSameInstant(ZoneOffset.UTC).toInstant()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.append(
|
||||||
|
"\$lte", LocalDateTime.of(dateTo, LocalTime.MIN)
|
||||||
|
.atZone(ZoneId.systemDefault())
|
||||||
|
.withZoneSameInstant(ZoneOffset.UTC).toInstant()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Document(
|
||||||
|
"\$group",
|
||||||
|
Document(
|
||||||
|
"_id",
|
||||||
|
Document("categoryId", "\$categoryDetails._id")
|
||||||
|
.append("categoryName", "\$categoryDetails.name")
|
||||||
|
.append("year", Document("\$year", "\$date"))
|
||||||
|
.append("month", Document("\$month", "\$date"))
|
||||||
|
)
|
||||||
|
.append("totalAmount", Document("\$sum", "\$amount"))
|
||||||
|
),
|
||||||
|
Document(
|
||||||
|
"\$group",
|
||||||
|
Document("_id", "\$_id.categoryId")
|
||||||
|
.append("categoryName", Document("\$first", "\$_id.categoryName"))
|
||||||
|
.append(
|
||||||
|
"monthlyData",
|
||||||
|
Document(
|
||||||
|
"\$push",
|
||||||
|
Document(
|
||||||
|
"month",
|
||||||
|
Document(
|
||||||
|
"\$concat", listOf(
|
||||||
|
Document("\$toString", "\$_id.year"), "-",
|
||||||
|
Document(
|
||||||
|
"\$cond", listOf(
|
||||||
|
Document("\$lt", listOf("\$_id.month", 10L)),
|
||||||
|
Document(
|
||||||
|
"\$concat", listOf(
|
||||||
|
"0",
|
||||||
|
Document("\$toString", "\$_id.month")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Document("\$toString", "\$_id.month")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.append("totalAmount", "\$totalAmount")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Document(
|
||||||
|
"\$addFields",
|
||||||
|
Document(
|
||||||
|
"completeMonthlyData",
|
||||||
|
Document(
|
||||||
|
"\$map",
|
||||||
|
Document("input", Document("\$range", listOf(0L, 6L)))
|
||||||
|
.append("as", "offset")
|
||||||
|
.append(
|
||||||
|
"in",
|
||||||
|
Document(
|
||||||
|
"month",
|
||||||
|
Document(
|
||||||
|
"\$dateToString",
|
||||||
|
Document("format", "%Y-%m")
|
||||||
|
.append(
|
||||||
|
"date",
|
||||||
|
Document(
|
||||||
|
"\$dateAdd",
|
||||||
|
Document("startDate", java.util.Date(1754006400000L))
|
||||||
|
.append("unit", "month")
|
||||||
|
.append(
|
||||||
|
"amount",
|
||||||
|
Document("\$multiply", listOf("\$\$offset", 1L))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.append(
|
||||||
|
"totalAmount",
|
||||||
|
Document(
|
||||||
|
"\$let",
|
||||||
|
Document(
|
||||||
|
"vars",
|
||||||
|
Document(
|
||||||
|
"matched",
|
||||||
|
Document(
|
||||||
|
"\$arrayElemAt", listOf(
|
||||||
|
Document(
|
||||||
|
"\$filter",
|
||||||
|
Document("input", "\$monthlyData")
|
||||||
|
.append("as", "data")
|
||||||
|
.append(
|
||||||
|
"cond",
|
||||||
|
Document(
|
||||||
|
"\$eq", listOf(
|
||||||
|
"\$\$data.month",
|
||||||
|
Document(
|
||||||
|
"\$dateToString",
|
||||||
|
Document("format", "%Y-%m")
|
||||||
|
.append(
|
||||||
|
"date",
|
||||||
|
Document(
|
||||||
|
"\$dateAdd",
|
||||||
|
Document(
|
||||||
|
"startDate",
|
||||||
|
java.util.Date(
|
||||||
|
1733011200000L
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.append(
|
||||||
|
"unit",
|
||||||
|
"month"
|
||||||
|
)
|
||||||
|
.append(
|
||||||
|
"amount",
|
||||||
|
Document(
|
||||||
|
"\$multiply",
|
||||||
|
listOf(
|
||||||
|
"\$\$offset",
|
||||||
|
1L
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
), 0L
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.append(
|
||||||
|
"in",
|
||||||
|
Document("\$ifNull", listOf("\$\$matched.totalAmount", 0L))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Document(
|
||||||
|
"\$project",
|
||||||
|
Document("_id", 0L)
|
||||||
|
.append("categoryId", "\$_id")
|
||||||
|
.append("categoryName", "\$categoryName")
|
||||||
|
.append("monthlyData", "\$completeMonthlyData")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return mongoTemplate.getCollection("transactions")
|
||||||
|
.flatMapMany { it.aggregate(pipeline) }
|
||||||
|
.map {
|
||||||
|
it["categoryId"] = it["categoryId"].toString()
|
||||||
|
it
|
||||||
|
}
|
||||||
|
.collectList()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/main/resources/application-dev-local.properties
Normal file
27
src/main/resources/application-dev-local.properties
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
spring.application.name=budger-app
|
||||||
|
|
||||||
|
spring.data.mongodb.host=localhost
|
||||||
|
spring.data.mongodb.port=27018
|
||||||
|
spring.data.mongodb.database=budger-app
|
||||||
|
#spring.data.mongodb.username=budger-app
|
||||||
|
#spring.data.mongodb.password=BA1q2w3e4r!
|
||||||
|
#spring.data.mongodb.authentication-database=admin
|
||||||
|
|
||||||
|
|
||||||
|
management.endpoints.web.exposure.include=*
|
||||||
|
management.endpoint.health.show-details=always
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
spring.datasource.url=jdbc:postgresql://213.183.51.243/familybudget_app
|
||||||
|
spring.datasource.username=familybudget_app
|
||||||
|
spring.datasource.password=FB1q2w3e4r!
|
||||||
|
|
||||||
|
|
||||||
|
# ??????? JDBC
|
||||||
|
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||||
|
|
||||||
|
# ????????? Hibernate
|
||||||
|
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
||||||
|
spring.jpa.hibernate.ddl-auto=none
|
||||||
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
spring.application.name=budger-app
|
spring.application.name=budger-app
|
||||||
|
|
||||||
spring.data.mongodb.host=213.226.71.138
|
spring.data.mongodb.host=77.222.32.64
|
||||||
spring.data.mongodb.port=27017
|
spring.data.mongodb.port=27017
|
||||||
spring.data.mongodb.database=budger-app
|
spring.data.mongodb.database=budger-app
|
||||||
spring.data.mongodb.username=budger-app
|
#spring.data.mongodb.username=budger-app
|
||||||
spring.data.mongodb.password=BA1q2w3e4r!
|
#spring.data.mongodb.password=BA1q2w3e4r!
|
||||||
spring.data.mongodb.authentication-database=admin
|
#spring.data.mongodb.authentication-database=admin
|
||||||
|
|
||||||
|
|
||||||
management.endpoints.web.exposure.include=*
|
management.endpoints.web.exposure.include=*
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
spring.application.name=budger-app
|
spring.application.name=budger-app
|
||||||
|
|
||||||
|
|
||||||
spring.data.mongodb.host=127.0.0.1
|
|
||||||
spring.data.mongodb.port=27017
|
spring.data.mongodb.uri=mongodb://budger-app:BA1q2w3e4r!@luminic.space:27017/budger-app?authSource=admin&minPoolSize=10&maxPoolSize=100
|
||||||
spring.data.mongodb.database=budger-app
|
|
||||||
spring.data.mongodb.username=budger-app
|
|
||||||
spring.data.mongodb.password=BA1q2w3e4r!
|
|
||||||
spring.data.mongodb.authentication-database=admin
|
|
||||||
|
|
||||||
|
|
||||||
spring.datasource.url=jdbc:postgresql://213.183.51.243/familybudget_app
|
logging.level.org.springframework.web=DEBUG
|
||||||
spring.datasource.username=familybudget_app
|
logging.level.org.springframework.data = DEBUG
|
||||||
spring.datasource.password=FB1q2w3e4r!
|
logging.level.org.springframework.data.mongodb.core.ReactiveMongoTemplate=DEBUG
|
||||||
|
logging.level.org.springframework.security = DEBUG
|
||||||
|
logging.level.org.springframework.data.mongodb.code = DEBUG
|
||||||
|
logging.level.org.springframework.web.reactive=DEBUG
|
||||||
|
|
||||||
|
#management.endpoints.web.exposure.include=*
|
||||||
|
#management.endpoint.metrics.access=read_only
|
||||||
|
|
||||||
|
|
||||||
logging.level.org.springframework.web=INFO
|
|
||||||
logging.level.org.springframework.data = INFO
|
|
||||||
logging.level.org.springframework.data.mongodb.core.ReactiveMongoTemplate=INFO
|
|
||||||
logging.level.org.springframework.security = INFO
|
|
||||||
logging.level.org.springframework.data.mongodb.code = INFO
|
|
||||||
logging.level.org.springframework.web.reactive=INFO<EFBFBD>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ server.port=8082
|
|||||||
#server.servlet.context-path=/api
|
#server.servlet.context-path=/api
|
||||||
spring.webflux.base-path=/api
|
spring.webflux.base-path=/api
|
||||||
|
|
||||||
spring.profiles.active=dev
|
spring.profiles.active=prod
|
||||||
spring.main.web-application-type=reactive
|
spring.main.web-application-type=reactive
|
||||||
|
|
||||||
|
|
||||||
@@ -20,10 +20,10 @@ server.compression.enabled=true
|
|||||||
server.compression.mime-types=application/json
|
server.compression.mime-types=application/json
|
||||||
|
|
||||||
|
|
||||||
# ??????? JDBC
|
# Expose prometheus, health, and info endpoints
|
||||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
#management.endpoints.web.exposure.include=prometheus,health,info
|
||||||
|
management.endpoints.web.exposure.include=*
|
||||||
|
|
||||||
# ????????? Hibernate
|
# Enable Prometheus metrics export
|
||||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
management.prometheus.metrics.export.enabled=true
|
||||||
spring.jpa.hibernate.ddl-auto=none
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user