+ analytics

This commit is contained in:
Vladimir Voronin
2025-01-23 00:16:17 +03:00
parent a0fa275073
commit 70f68d4046
2 changed files with 198 additions and 1 deletions

View File

@@ -20,6 +20,7 @@ import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneId
import java.time.ZoneOffset
import java.util.Calendar
import java.util.Date
@Service
@@ -376,7 +377,7 @@ class CategoryService(
"date",
Document(
"\$gte", Date.from(
LocalDateTime.of(dateTo, LocalTime.MIN)
LocalDateTime.of(dateFrom, LocalTime.MIN)
.atZone(ZoneId.systemDefault())
.withZoneSameInstant(ZoneOffset.UTC).toInstant()
)
@@ -545,5 +546,195 @@ class CategoryService(
.collectList()
}
fun getCategorySummaries(dateFrom: LocalDate): Mono<List<Document>> {
val sixMonthsAgo = Date.from(
LocalDateTime.of(dateFrom, LocalTime.MIN)
.atZone(ZoneId.systemDefault())
.withZoneSameInstant(ZoneOffset.UTC).toInstant()
) // Пример даты, можно заменить на вычисляемую
val aggregation = listOf(
// 1. Фильтр за последние 6 месяцев
Document(
"\$match",
Document("date", Document("\$gte", sixMonthsAgo).append("\$lt", Date())).append("type.code", "INSTANT")
),
// 2. Группируем по категории + (год, месяц)
Document(
"\$group", Document(
"_id", Document("category", "\$category.\$id")
.append("year", Document("\$year", "\$date"))
.append("month", Document("\$month", "\$date"))
)
.append("totalAmount", Document("\$sum", "\$amount"))
),
// 3. Подтягиваем информацию о категории
Document(
"\$lookup", Document("from", "categories")
.append("localField", "_id.category")
.append("foreignField", "_id")
.append("as", "categoryInfo")
),
// 4. Распаковываем массив категорий
Document("\$unwind", "\$categoryInfo"),
// 5. Фильтруем по типу категории (EXPENSE)
Document("\$match", Document("categoryInfo.type.code", "EXPENSE")),
// 6. Группируем обратно по категории, собирая все (год, месяц, total)
Document(
"\$group", Document("_id", "\$_id.category")
.append("categoryName", Document("\$first", "\$categoryInfo.name"))
.append("categoryType", Document("\$first", "\$categoryInfo.type.code"))
.append("categoryIcon", Document("\$first", "\$categoryInfo.icon"))
.append(
"monthlySums", Document(
"\$push", Document("year", "\$_id.year")
.append("month", "\$_id.month")
.append("total", "\$totalAmount")
)
)
),
// 7. Формируем единый массив из 6 элементов:
// - каждый элемент = {year, month, total},
// - если нет записей за месяц, ставим total=0
Document(
"\$project", Document("categoryName", 1)
.append("categoryType", 1)
.append("categoryIcon", 1)
.append(
"monthlySums", Document(
"\$map", Document("input", Document("\$range", listOf(0, 6)))
.append("as", "i")
.append(
"in", Document(
"\$let", Document(
"vars", Document(
"subDate", Document(
"\$dateSubtract", Document("startDate", Date())
.append("unit", "month")
.append("amount", "$\$i")
)
)
)
.append(
"in", Document("year", Document("\$year", "$\$subDate"))
.append("month", Document("\$month", "$\$subDate"))
.append(
"total", Document(
"\$ifNull", listOf(
Document(
"\$getField", Document("field", "total")
.append(
"input", Document(
"\$arrayElemAt", listOf(
Document(
"\$filter",
Document(
"input",
"\$monthlySums"
)
.append("as", "ms")
.append(
"cond", Document(
"\$and", listOf(
Document(
"\$eq",
listOf(
"$\$ms.year",
Document(
"\$year",
"$\$subDate"
)
)
),
Document(
"\$eq",
listOf(
"$\$ms.month",
Document(
"\$month",
"$\$subDate"
)
)
)
)
)
)
), 0.0
)
)
)
), 0.0
)
)
)
)
)
)
)
)
),
// 8. Сортируем результат по имени категории
Document("\$sort", Document("categoryName", 1))
)
// Выполняем агрегацию
return mongoTemplate.getCollection("transactions")
.flatMapMany { it.aggregate(aggregation) }
.map { document ->
// Преобразуем _id в строку
document["_id"] = document["_id"].toString()
// Получаем monthlySums и приводим к изменяемому списку
val monthlySums = (document["monthlySums"] as? List<*>)?.map { monthlySum ->
if (monthlySum is Document) {
// Создаем копию Document, чтобы избежать изменений в исходном списке
Document(monthlySum).apply {
// Добавляем поле date
val date = LocalDate.of(getInteger("year"), getInteger("month"), 1)
this["date"] = date
}
} else {
monthlySum
}
}?.toMutableList()
// Сортируем monthlySums по полю date
val sortedMonthlySums = monthlySums?.sortedBy { (it as? Document)?.get("date") as? LocalDate }
// Рассчитываем разницу между текущим и предыдущим месяцем
var previousMonthSum = 0.0
sortedMonthlySums?.forEach { monthlySum ->
if (monthlySum is Document) {
val currentMonthSum = monthlySum.getDouble("total") ?: 0.0
// Рассчитываем разницу в процентах
val difference = if (previousMonthSum != 0.0 && currentMonthSum != 0.0) {
(((currentMonthSum - previousMonthSum) / previousMonthSum) * 100).toInt()
} else {
0
}
// Добавляем поле difference
monthlySum["difference"] = difference
// Обновляем previousMonthSum для следующей итерации
previousMonthSum = currentMonthSum
}
}
// Обновляем документ с отсортированными и обновленными monthlySums
document["monthlySums"] = sortedMonthlySums
document
}
.collectList()
}
}