From 195bdd83f017e59251ae3d0e14bc1fe729413ca4 Mon Sep 17 00:00:00 2001 From: xds Date: Tue, 18 Nov 2025 00:34:02 +0300 Subject: [PATCH] filters for transactions; update transactions when recurrent updated --- .../finance/api/TransactionController.kt | 6 +- .../luminic/finance/models/Transaction.kt | 2 +- .../repos/RecurrentOperationRepoImpl.kt | 2 +- .../luminic/finance/repos/TransactionRepo.kt | 5 +- .../finance/repos/TransactionRepoImpl.kt | 123 ++++++++++-- .../services/CategoryServiceMongoImpl.kt | 108 ---------- .../services/CurrencyServiceMongoImpl.kt | 51 ----- .../services/RecurrentOperationServiceImpl.kt | 28 ++- .../finance/services/SpaceServiceMongoImpl.kt | 145 -------------- .../finance/services/TransactionService.kt | 5 + .../services/TransactionServiceImpl.kt | 5 +- .../services/TransactionServiceMongoImpl.kt | 185 ------------------ .../finance/services/gpt/CategorizeService.kt | 16 +- .../gpt/DeepSeekCategorizationService.kt | 4 +- .../finance/services/telegram/BotService.kt | 2 +- .../telegram/TransactionsServiceImpl.kt | 1 - 16 files changed, 162 insertions(+), 526 deletions(-) delete mode 100644 src/main/kotlin/space/luminic/finance/services/CategoryServiceMongoImpl.kt delete mode 100644 src/main/kotlin/space/luminic/finance/services/CurrencyServiceMongoImpl.kt delete mode 100644 src/main/kotlin/space/luminic/finance/services/SpaceServiceMongoImpl.kt delete mode 100644 src/main/kotlin/space/luminic/finance/services/TransactionServiceMongoImpl.kt diff --git a/src/main/kotlin/space/luminic/finance/api/TransactionController.kt b/src/main/kotlin/space/luminic/finance/api/TransactionController.kt index b9b3530..d6adb11 100644 --- a/src/main/kotlin/space/luminic/finance/api/TransactionController.kt +++ b/src/main/kotlin/space/luminic/finance/api/TransactionController.kt @@ -21,9 +21,9 @@ class TransactionController ( ){ - @GetMapping - fun getTransactions(@PathVariable spaceId: Int) : List{ - return transactionService.getTransactions(spaceId, TransactionService.TransactionsFilter(),"date", "DESC").map { it.toDto() } + @PostMapping("/_search") + fun getTransactions(@PathVariable spaceId: Int, @RequestBody filter: TransactionService.TransactionsFilter) : List{ + return transactionService.getTransactions(spaceId, filter,"date", "DESC").map { it.toDto() } } @GetMapping("/{transactionId}") diff --git a/src/main/kotlin/space/luminic/finance/models/Transaction.kt b/src/main/kotlin/space/luminic/finance/models/Transaction.kt index 152d97a..bf35d2f 100644 --- a/src/main/kotlin/space/luminic/finance/models/Transaction.kt +++ b/src/main/kotlin/space/luminic/finance/models/Transaction.kt @@ -15,7 +15,7 @@ data class Transaction( val type: TransactionType = TransactionType.EXPENSE, val kind: TransactionKind = TransactionKind.INSTANT, val category: Category? = null, - val comment: String, + var comment: String, val amount: BigDecimal, val fees: BigDecimal = BigDecimal.ZERO, val date: LocalDate = LocalDate.now(), diff --git a/src/main/kotlin/space/luminic/finance/repos/RecurrentOperationRepoImpl.kt b/src/main/kotlin/space/luminic/finance/repos/RecurrentOperationRepoImpl.kt index b0bd8a6..7508e4a 100644 --- a/src/main/kotlin/space/luminic/finance/repos/RecurrentOperationRepoImpl.kt +++ b/src/main/kotlin/space/luminic/finance/repos/RecurrentOperationRepoImpl.kt @@ -74,7 +74,7 @@ class RecurrentOperationRepoImpl( join finance.categories c on ro.category_id = c.id join finance.users r_created_by on ro.created_by_id = r_created_by.id where ro.space_id = :spaceId - order by ro.date + order by ro.date, ro.id """.trimIndent() val params = mapOf("spaceId" to spaceId) return jdbcTemplate.query(sql, params, operationRowMapper()) diff --git a/src/main/kotlin/space/luminic/finance/repos/TransactionRepo.kt b/src/main/kotlin/space/luminic/finance/repos/TransactionRepo.kt index de2b7c9..813584a 100644 --- a/src/main/kotlin/space/luminic/finance/repos/TransactionRepo.kt +++ b/src/main/kotlin/space/luminic/finance/repos/TransactionRepo.kt @@ -1,13 +1,16 @@ package space.luminic.finance.repos import space.luminic.finance.models.Transaction +import space.luminic.finance.services.TransactionService interface TransactionRepo { - fun findAllBySpaceId(spaceId: Int): List + fun findAllBySpaceId(spaceId: Int, filters: TransactionService.TransactionsFilter): List fun findBySpaceIdAndId(spaceId: Int, id: Int): Transaction? + fun findBySpaceIdAndRecurrentId(spaceId: Int, recurrentId: Int): List fun create(transaction: Transaction, userId: Int): Int fun createBatch(transactions: List, userId: Int) fun update(transaction: Transaction): Int + fun updateBatch(transactions: List, userId: Int) fun delete(transactionId: Int) fun deleteByRecurrentId(spaceId: Int, recurrentId: Int) diff --git a/src/main/kotlin/space/luminic/finance/repos/TransactionRepoImpl.kt b/src/main/kotlin/space/luminic/finance/repos/TransactionRepoImpl.kt index 867e26c..796399b 100644 --- a/src/main/kotlin/space/luminic/finance/repos/TransactionRepoImpl.kt +++ b/src/main/kotlin/space/luminic/finance/repos/TransactionRepoImpl.kt @@ -6,6 +6,7 @@ import org.springframework.stereotype.Repository import space.luminic.finance.models.Category import space.luminic.finance.models.Transaction import space.luminic.finance.models.User +import space.luminic.finance.services.TransactionService @Repository class TransactionRepoImpl( @@ -52,8 +53,8 @@ class TransactionRepoImpl( ) } - override fun findAllBySpaceId(spaceId: Int): List { - val sql = """ + override fun findAllBySpaceId(spaceId: Int, filters: TransactionService.TransactionsFilter): List { + var sql = """ SELECT t.id AS t_id, t.parent_id AS t_parent_id, @@ -86,11 +87,39 @@ class TransactionRepoImpl( LEFT JOIN finance.categories c ON t.category_id = c.id JOIN finance.users u ON u.id = t.created_by_id WHERE t.space_id = :spaceId and t.is_deleted = false - ORDER BY t.date, t.id + """.trimIndent() - val params = mapOf( + val params = mutableMapOf( "spaceId" to spaceId, + "offset" to filters.offset, + "limit" to filters.limit, ) + filters.type?.let { + sql += " AND t.type = :type" + params.put("type", it.name) + } + filters.kind?.let { + sql += " AND t.kind = :kind" + params.put("kind", it.name) + } + filters.isDone?.let { + sql += " AND t.is_done = :isDone" + params.put("isDone", it) + } + filters.dateFrom?.let { + sql += " AND t.date >= :dateFrom" + params.put("dateFrom", it) + } + filters.dateTo?.let { + sql += " AND t.date <= :dateTo" + params.put("dateTo", it) + } + sql += """ + ORDER BY t.date, t.id + OFFSET :offset ROWS + FETCH FIRST :limit ROWS ONLY""" + + return jdbcTemplate.query(sql, params, transactionRowMapper()) } @@ -134,6 +163,49 @@ class TransactionRepoImpl( return jdbcTemplate.query(sql, params, transactionRowMapper()).firstOrNull() } + override fun findBySpaceIdAndRecurrentId( + spaceId: Int, + recurrentId: Int + ): List { + val sql = """SELECT + t.id AS t_id, + t.parent_id AS t_parent_id, + t.space_id AS t_space_id, + t.type AS t_type, + t.kind AS t_kind, + t.comment AS t_comment, + t.amount AS t_amount, + t.fees AS t_fees, + t.date AS t_date, + t.is_deleted AS t_is_deleted, + t.is_done AS t_is_done, + t.created_at AS t_created_at, + t.updated_at AS t_updated_at, + t.tg_chat_id AS tg_chat_id, + t.tg_message_id AS tg_message_id, + c.id AS c_id, + c.type AS c_type, + c.name AS c_name, + c.description AS c_description, + c.icon AS c_icon, + c.is_deleted AS c_is_deleted, + c.created_at AS c_created_at, + c.updated_at AS c_updated_at, + u.id AS u_id, + u.username AS u_username, + u.first_name AS u_first_name, + t.recurrent_id AS t_recurrent_id + FROM finance.transactions t + LEFT JOIN finance.categories c ON t.category_id = c.id + JOIN finance.users u ON u.id = t.created_by_id + WHERE t.space_id = :spaceId and t.recurrent_id = :recurrentId and t.is_deleted = false""".trimMargin() + val params = mapOf( + "spaceId" to spaceId, + "recurrentId" to recurrentId, + ) + return jdbcTemplate.query(sql, params, transactionRowMapper()) + } + override fun create(transaction: Transaction, userId: Int): Int { val sql = """ INSERT INTO finance.transactions (space_id, parent_id, type, kind, category_id, comment, amount, fees, date, is_deleted, is_done, created_by_id, tg_chat_id, tg_message_id, recurrent_id) VALUES ( @@ -211,14 +283,6 @@ class TransactionRepoImpl( } override fun update(transaction: Transaction): Int { -// val type: TransactionType = TransactionType.EXPENSE, -// val kind: TransactionKind = TransactionKind.INSTANT, -// val category: Int, -// val comment: String, -// val amount: BigDecimal, -// val fees: BigDecimal = BigDecimal.ZERO, -// val isDone: Boolean, -// val date: Instant val sql = """ UPDATE finance.transactions @@ -247,6 +311,39 @@ class TransactionRepoImpl( return transaction.id!! } + override fun updateBatch(transactions: List, userId: Int) { + val sql = """ + UPDATE finance.transactions + set type = :type, + kind = :kind, + category_id = :categoryId, + comment = :comment, + amount = :amount, + fees = :fees, + is_done = :is_done, + date = :date, + updated_by_id = :updatedById, + updated_at = now() + where id = :id + """.trimIndent() + val batchValues = transactions.map { transaction -> + mapOf( + "id" to transaction.id, + "type" to transaction.type.name, + "kind" to transaction.kind.name, + "categoryId" to transaction.category?.id, + "comment" to transaction.comment, + "amount" to transaction.amount, + "fees" to transaction.fees, + "date" to transaction.date, + "is_done" to transaction.isDone, + "updatedById" to userId, + ) + }.toTypedArray() + jdbcTemplate.batchUpdate(sql, batchValues) + + } + override fun delete(transactionId: Int) { val sql = """ update finance.transactions set is_deleted = true where id = :id @@ -258,7 +355,7 @@ class TransactionRepoImpl( } override fun deleteByRecurrentId(spaceId: Int, recurrentId: Int) { - val sql = """ + val sql = """ update finance.transactions set is_deleted = true where recurrent_id = :recurrentId """.trimIndent() val params = mapOf( diff --git a/src/main/kotlin/space/luminic/finance/services/CategoryServiceMongoImpl.kt b/src/main/kotlin/space/luminic/finance/services/CategoryServiceMongoImpl.kt deleted file mode 100644 index 538a4a4..0000000 --- a/src/main/kotlin/space/luminic/finance/services/CategoryServiceMongoImpl.kt +++ /dev/null @@ -1,108 +0,0 @@ -//package space.luminic.finance.services -// -//import kotlinx.coroutines.reactive.awaitSingle -//import org.bson.types.ObjectId -//import org.springframework.data.mongodb.core.ReactiveMongoTemplate -//import org.springframework.data.mongodb.core.aggregation.Aggregation.addFields -//import org.springframework.data.mongodb.core.aggregation.Aggregation.lookup -//import org.springframework.data.mongodb.core.aggregation.Aggregation.match -//import org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation -//import org.springframework.data.mongodb.core.aggregation.Aggregation.unwind -//import org.springframework.data.mongodb.core.aggregation.AggregationOperation -//import org.springframework.data.mongodb.core.aggregation.ConvertOperators -//import org.springframework.data.mongodb.core.query.Criteria -//import org.springframework.stereotype.Service -//import space.luminic.finance.dtos.CategoryDTO -//import space.luminic.finance.models.Category -//import space.luminic.finance.repos.CategoryEtalonRepo -//import space.luminic.finance.repos.CategoryRepo -// -//@Service -//class CategoryServiceMongoImpl( -// private val categoryRepo: CategoryRepo, -// private val categoryEtalonRepo: CategoryEtalonRepo, -// private val reactiveMongoTemplate: ReactiveMongoTemplate, -// private val authService: AuthService, -//) : CategoryService { -// -// private fun basicAggregation(spaceId: String): List { -// val addFieldsAsOJ = addFields() -// .addField("createdByOI") -// .withValue(ConvertOperators.valueOf("createdById").convertToObjectId()) -// .addField("updatedByOI") -// .withValue(ConvertOperators.valueOf("updatedById").convertToObjectId()) -// .build() -// val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy") -// val unwindCreatedBy = unwind("createdBy") -// -// val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy") -// val unwindUpdatedBy = unwind("updatedBy") -// val matchCriteria = mutableListOf() -// matchCriteria.add(Criteria.where("spaceId").`is`(spaceId)) -// matchCriteria.add(Criteria.where("isDeleted").`is`(false)) -// val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray())) -// -// return listOf(addFieldsAsOJ, lookupCreatedBy, unwindCreatedBy, lookupUpdatedBy, unwindUpdatedBy, matchStage) -// } -// -// override suspend fun getCategories(spaceId: String): List { -// val basicAggregation = basicAggregation(spaceId) -// val aggregation = newAggregation(*basicAggregation.toTypedArray()) -// return reactiveMongoTemplate.aggregate(aggregation, "categories", Category::class.java).collectList().awaitSingle() -// } -// -// override suspend fun getCategory(spaceId: String, id: String): Category { -// val basicAggregation = basicAggregation(spaceId) -// val match = match(Criteria.where("_id").`is`(ObjectId(id))) -// val aggregation = newAggregation(*basicAggregation.toTypedArray(), match) -// return reactiveMongoTemplate.aggregate(aggregation, "categories", Category::class.java).awaitSingle() -// } -// -// -// override suspend fun createCategory( -// spaceId: String, -// category: CategoryDTO.CreateCategoryDTO -// ): Category { -// val createdCategory = Category( -// spaceId = spaceId, -// type = category.type, -// name = category.name, -// icon = category.icon -// ) -// return categoryRepo.save(createdCategory).awaitSingle() -// } -// -// override suspend fun updateCategory( -// spaceId: String, -// category: CategoryDTO.UpdateCategoryDTO -// ): Category { -// val existingCategory = getCategory(spaceId, category.id) -// val updatedCategory = existingCategory.copy( -// type = category.type, -// name = category.name, -// icon = category.icon, -// ) -// return categoryRepo.save(updatedCategory).awaitSingle() -// } -// -// override suspend fun deleteCategory(spaceId: String, id: String) { -// val existingCategory = getCategory(spaceId, id) -// existingCategory.isDeleted = true -// categoryRepo.save(existingCategory).awaitSingle() -// } -// -// override suspend fun createCategoriesForSpace(spaceId: String): List { -// val etalonCategories = categoryEtalonRepo.findAll().collectList().awaitSingle() -// val toCreate = etalonCategories.map { -// Category( -// spaceId = spaceId, -// type = it.type, -// name = it.name, -// icon = it.icon -// ) -// } -// return categoryRepo.saveAll(toCreate).collectList().awaitSingle() -// } -// -// -//} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/CurrencyServiceMongoImpl.kt b/src/main/kotlin/space/luminic/finance/services/CurrencyServiceMongoImpl.kt deleted file mode 100644 index f769cbf..0000000 --- a/src/main/kotlin/space/luminic/finance/services/CurrencyServiceMongoImpl.kt +++ /dev/null @@ -1,51 +0,0 @@ -//package space.luminic.finance.services -// -//import kotlinx.coroutines.reactive.awaitSingle -//import org.springframework.stereotype.Service -//import space.luminic.finance.dtos.CurrencyDTO -//import space.luminic.finance.models.Currency -//import space.luminic.finance.models.CurrencyRate -//import space.luminic.finance.repos.CurrencyRateRepo -//import space.luminic.finance.repos.CurrencyRepo -//import java.math.BigDecimal -//import java.time.LocalDate -// -//@Service -//class CurrencyServiceMongoImpl( -// private val currencyRepo: CurrencyRepo, -// private val currencyRateRepo: CurrencyRateRepo -//) : CurrencyService { -// -// override suspend fun getCurrencies(): List { -// return currencyRepo.findAll().collectList().awaitSingle() -// } -// -// override suspend fun getCurrency(currencyCode: String): Currency { -// return currencyRepo.findById(currencyCode).awaitSingle() -// } -// -// override suspend fun createCurrency(currency: CurrencyDTO): Currency { -// val createdCurrency = Currency(currency.code, currency.name, currency.symbol) -// return currencyRepo.save(createdCurrency).awaitSingle() -// } -// -// override suspend fun updateCurrency(currency: CurrencyDTO): Currency { -// val existingCurrency = currencyRepo.findById(currency.code).awaitSingle() -// val newCurrency = existingCurrency.copy(name = currency.name, symbol = currency.symbol) -// return currencyRepo.save(newCurrency).awaitSingle() -// } -// -// override suspend fun deleteCurrency(currencyCode: String) { -// currencyRepo.deleteById(currencyCode).awaitSingle() -// } -// -// override suspend fun createCurrencyRate(currencyCode: String): CurrencyRate { -// return currencyRateRepo.save( -// CurrencyRate( -// currencyCode = currencyCode, -// rate = BigDecimal(12.0), -// date = LocalDate.now(), -// ) -// ).awaitSingle() -// } -//} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/RecurrentOperationServiceImpl.kt b/src/main/kotlin/space/luminic/finance/services/RecurrentOperationServiceImpl.kt index b3e6e1c..75f3463 100644 --- a/src/main/kotlin/space/luminic/finance/services/RecurrentOperationServiceImpl.kt +++ b/src/main/kotlin/space/luminic/finance/services/RecurrentOperationServiceImpl.kt @@ -15,6 +15,7 @@ import space.luminic.finance.repos.RecurrentOperationRepo import space.luminic.finance.repos.SpaceRepo import space.luminic.finance.repos.TransactionRepo import java.time.LocalDate +import kotlin.math.min @Service class RecurrentOperationServiceImpl( @@ -22,7 +23,6 @@ class RecurrentOperationServiceImpl( private val spaceRepo: SpaceRepo, private val recurrentOperationRepo: RecurrentOperationRepo, private val categoryService: CategoryService, - private val transactionService: TransactionService, private val transactionRepo: TransactionRepo ) : RecurrentOperationService { private val logger = LoggerFactory.getLogger(this.javaClass) @@ -61,7 +61,8 @@ class RecurrentOperationServiceImpl( val transactionsToCreate = mutableListOf() serviceScope.launch { runCatching { - val date = LocalDate.now() + val now = LocalDate.now() + val date = now.withDayOfMonth(min(operation.date, now.lengthOfMonth())) for (i in 1..12) { transactionsToCreate.add( Transaction( @@ -99,7 +100,30 @@ class RecurrentOperationServiceImpl( date = operation.date ) recurrentOperationRepo.update(updatedOperation, userId) + serviceScope.launch { + val transactionsToUpdate = mutableListOf() + runCatching { + val txs = transactionRepo.findBySpaceIdAndRecurrentId(spaceId, operationId) + txs.forEach { + transactionsToUpdate.add( + it.copy( + type = if (it.category?.type == Category.CategoryType.EXPENSE) Transaction.TransactionType.EXPENSE else Transaction.TransactionType.INCOME, + category = updatedOperation.category, + comment = operation.name, + date = LocalDate.of( + it.date.year, + it.date.monthValue, + min(it.date.lengthOfMonth(), updatedOperation.date) + ) + ) + ) + } + transactionRepo.updateBatch(transactionsToUpdate, userId) + }.onFailure { + logger.error("Error creating recurring operation", it) + } + } } override fun delete(spaceId: Int, id: Int) { diff --git a/src/main/kotlin/space/luminic/finance/services/SpaceServiceMongoImpl.kt b/src/main/kotlin/space/luminic/finance/services/SpaceServiceMongoImpl.kt deleted file mode 100644 index f9423a4..0000000 --- a/src/main/kotlin/space/luminic/finance/services/SpaceServiceMongoImpl.kt +++ /dev/null @@ -1,145 +0,0 @@ -//package space.luminic.finance.services -// -//import com.mongodb.client.model.Aggregates.sort -//import kotlinx.coroutines.reactive.awaitFirst -//import kotlinx.coroutines.reactive.awaitFirstOrNull -//import kotlinx.coroutines.reactive.awaitSingle -//import org.springframework.data.domain.Sort -//import org.springframework.data.mongodb.core.ReactiveMongoTemplate -//import org.springframework.data.mongodb.core.aggregation.Aggregation.* -//import org.springframework.data.mongodb.core.aggregation.AggregationOperation -// -//import org.springframework.data.mongodb.core.aggregation.ConvertOperators -//import org.springframework.data.mongodb.core.aggregation.VariableOperators -//import org.springframework.data.mongodb.core.query.Criteria -//import org.springframework.stereotype.Service -//import space.luminic.finance.dtos.SpaceDTO -//import space.luminic.finance.models.NotFoundException -//import space.luminic.finance.models.Space -//import space.luminic.finance.models.User -//import space.luminic.finance.repos.SpaceRepo -// -//@Service -//class SpaceServiceMongoImpl( -// private val authService: AuthService, -// private val spaceRepo: SpaceRepo, -// private val mongoTemplate: ReactiveMongoTemplate, -//) : SpaceService { -// -// private fun basicAggregation(user: User): List { -// val addFieldsAsOJ = addFields() -// .addField("createdByOI") -// .withValue(ConvertOperators.valueOf("createdById").convertToObjectId()) -// .addField("updatedByOI") -// .withValue(ConvertOperators.valueOf("updatedById").convertToObjectId()) -// .build() -// val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy") -// val unwindCreatedBy = unwind("createdBy") -// -// val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy") -// val unwindUpdatedBy = unwind("updatedBy") -// -// -// -// val matchCriteria = mutableListOf() -// matchCriteria.add( -// Criteria().orOperator( -// Criteria.where("ownerId").`is`(user.id), -// Criteria.where("participantsIds").`is`(user.id) -// ) -// ) -// matchCriteria.add(Criteria.where("isDeleted").`is`(false)) -// val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray())) -// -// return listOf(addFieldsAsOJ, lookupCreatedBy, unwindCreatedBy, lookupUpdatedBy, unwindUpdatedBy, matchStage) -// } -// -// private fun ownerAndParticipantsLookups(): List{ -// val addOwnerAsOJ = addFields() -// .addField("ownerIdAsObjectId") -// .withValue(ConvertOperators.valueOf("ownerId").convertToObjectId()) -// .addField("participantsIdsAsObjectId") -// .withValue( -// VariableOperators.Map.itemsOf("participantsIds") -// .`as`("id") -// .andApply( -// ConvertOperators.valueOf("$\$id").convertToObjectId() -// ) -// ) -// .build() -// val lookupOwner = lookup("users", "ownerIdAsObjectId", "_id", "owner") -// val unwindOwner = unwind("owner") -// val lookupUsers = lookup("users", "participantsIdsAsObjectId", "_id", "participants") -// return listOf(addOwnerAsOJ, lookupOwner, unwindOwner, lookupUsers) -// } -// -// override suspend fun checkSpace(spaceId: String): Space { -// val user = authService.getSecurityUser() -// val space = getSpace(spaceId) -// -// // Проверяем доступ пользователя к пространству -// return if (space.participants!!.none { it.id.toString() == user.id }) { -// throw IllegalArgumentException("User does not have access to this Space") -// } else space -// } -// -// override suspend fun getSpaces(): List { -// val user = authService.getSecurityUser() -// val basicAggregation = basicAggregation(user) -// val ownerAndParticipantsLookup = ownerAndParticipantsLookups() -// -// val sort = sort(Sort.by(Sort.Direction.DESC, "createdAt")) -// val aggregation = newAggregation( -// *basicAggregation.toTypedArray(), -// *ownerAndParticipantsLookup.toTypedArray(), -// sort, -// ) -// return mongoTemplate.aggregate(aggregation, "spaces", Space::class.java).collectList().awaitSingle() -// } -// -// override suspend fun getSpace(id: String): Space { -// val user = authService.getSecurityUser() -// val basicAggregation = basicAggregation(user) -// val ownerAndParticipantsLookup = ownerAndParticipantsLookups() -// -// val aggregation = newAggregation( -// *basicAggregation.toTypedArray(), -// *ownerAndParticipantsLookup.toTypedArray(), -// ) -// return mongoTemplate.aggregate(aggregation, "spaces", Space::class.java).awaitFirstOrNull() -// ?: throw NotFoundException("Space not found") -// -// } -// -// override suspend fun createSpace(space: SpaceDTO.CreateSpaceDTO): Space { -// val owner = authService.getSecurityUser() -// val createdSpace = Space( -// name = space.name, -// ownerId = owner.id!!, -// -// participantsIds = listOf(owner.id!!), -// -// -// ) -// createdSpace.owner = owner -// createdSpace.participants?.toMutableList()?.add(owner) -// val savedSpace = spaceRepo.save(createdSpace).awaitSingle() -// return savedSpace -// } -// -// override suspend fun updateSpace(spaceId: String, space: SpaceDTO.UpdateSpaceDTO): Space { -// val existingSpace = spaceRepo.findById(spaceId).awaitFirstOrNull() ?: throw NotFoundException("Space not found") -// val updatedSpace = existingSpace.copy( -// name = space.name, -// ) -// updatedSpace.owner = existingSpace.owner -// updatedSpace.participants = existingSpace.participants -// return spaceRepo.save(updatedSpace).awaitFirst() -// } -// -// override suspend fun deleteSpace(spaceId: String) { -// val space = spaceRepo.findById(spaceId).awaitFirstOrNull() ?: throw NotFoundException("Space not found") -// space.isDeleted = true -// spaceRepo.save(space).awaitFirst() -// } -//} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/TransactionService.kt b/src/main/kotlin/space/luminic/finance/services/TransactionService.kt index 20e09c8..bb2522d 100644 --- a/src/main/kotlin/space/luminic/finance/services/TransactionService.kt +++ b/src/main/kotlin/space/luminic/finance/services/TransactionService.kt @@ -7,8 +7,13 @@ import java.time.LocalDate interface TransactionService { data class TransactionsFilter( + val type: Transaction.TransactionType? = null, + val kind: Transaction.TransactionKind? = null, val dateFrom: LocalDate? = null, val dateTo: LocalDate? = null, + val isDone: Boolean? = null, + val offset: Int = 0, + val limit: Int = 10, ) fun getTransactions( diff --git a/src/main/kotlin/space/luminic/finance/services/TransactionServiceImpl.kt b/src/main/kotlin/space/luminic/finance/services/TransactionServiceImpl.kt index 2b578e7..a85e9be 100644 --- a/src/main/kotlin/space/luminic/finance/services/TransactionServiceImpl.kt +++ b/src/main/kotlin/space/luminic/finance/services/TransactionServiceImpl.kt @@ -21,7 +21,7 @@ class TransactionServiceImpl( sortBy: String, sortDirection: String ): List { - val transactions = transactionRepo.findAllBySpaceId(spaceId) + val transactions = transactionRepo.findAllBySpaceId(spaceId, filter) return transactions } @@ -106,9 +106,10 @@ class TransactionServiceImpl( tgChatId = existingTransaction.tgChatId, tgMessageId = existingTransaction.tgMessageId, ) - if (existingTransaction.category == null && updatedTransaction.category != null) { + if ((existingTransaction.category == null && updatedTransaction.category != null) || (existingTransaction.category?.id != updatedTransaction.category?.id)) { categorizeService.notifyThatCategorySelected(updatedTransaction) } + return transactionRepo.update(updatedTransaction) } diff --git a/src/main/kotlin/space/luminic/finance/services/TransactionServiceMongoImpl.kt b/src/main/kotlin/space/luminic/finance/services/TransactionServiceMongoImpl.kt deleted file mode 100644 index f82276c..0000000 --- a/src/main/kotlin/space/luminic/finance/services/TransactionServiceMongoImpl.kt +++ /dev/null @@ -1,185 +0,0 @@ -//package space.luminic.finance.services -// -//import kotlinx.coroutines.reactive.awaitFirstOrNull -//import kotlinx.coroutines.reactive.awaitSingle -//import org.bson.types.ObjectId -//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.aggregation.Aggregation.addFields -//import org.springframework.data.mongodb.core.aggregation.Aggregation.lookup -//import org.springframework.data.mongodb.core.aggregation.Aggregation.match -//import org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation -//import org.springframework.data.mongodb.core.aggregation.Aggregation.sort -//import org.springframework.data.mongodb.core.aggregation.Aggregation.unwind -//import org.springframework.data.mongodb.core.aggregation.AggregationOperation -//import org.springframework.data.mongodb.core.aggregation.ConvertOperators -//import org.springframework.data.mongodb.core.query.Criteria -//import org.springframework.stereotype.Service -//import space.luminic.finance.dtos.TransactionDTO -//import space.luminic.finance.models.NotFoundException -//import space.luminic.finance.models.Transaction -//import space.luminic.finance.repos.TransactionRepo -// -//@Service -//class TransactionServiceMongoImpl( -// private val mongoTemplate: ReactiveMongoTemplate, -// private val transactionRepo: TransactionRepo, -// private val categoryService: CategoryService, -//) : TransactionService { -// -// -// private fun basicAggregation(spaceId: String): List { -// val addFieldsOI = addFields() -// .addField("createdByOI") -// .withValue(ConvertOperators.valueOf("createdById").convertToObjectId()) -// .addField("updatedByOI") -// .withValue(ConvertOperators.valueOf("updatedById").convertToObjectId()) -// .addField("fromAccountIdOI") -// .withValue(ConvertOperators.valueOf("fromAccountId").convertToObjectId()) -// .addField("toAccountIdOI") -// .withValue(ConvertOperators.valueOf("toAccountId").convertToObjectId()) -// .addField("categoryIdOI") -// .withValue(ConvertOperators.valueOf("categoryId").convertToObjectId()) -// .build() -// -// val lookupFromAccount = lookup("accounts", "fromAccountIdOI", "_id", "fromAccount") -// val unwindFromAccount = unwind("fromAccount") -// val lookupToAccount = lookup("accounts", "toAccountIdOI", "_id", "toAccount") -// val unwindToAccount = unwind("toAccount", true) -// -// val lookupCategory = lookup("categories", "categoryIdOI", "_id", "category") -// val unwindCategory = unwind("category") -// -// val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy") -// val unwindCreatedBy = unwind("createdBy") -// -// val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy") -// val unwindUpdatedBy = unwind("updatedBy") -// val matchCriteria = mutableListOf() -// matchCriteria.add(Criteria.where("spaceId").`is`(spaceId)) -// val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray())) -// -// return listOf( -// matchStage, -// addFieldsOI, -// lookupFromAccount, -// unwindFromAccount, -// lookupToAccount, -// unwindToAccount, -// lookupCategory, -// unwindCategory, -// lookupCreatedBy, -// unwindCreatedBy, -// lookupUpdatedBy, -// unwindUpdatedBy -// ) -// } -// -// override suspend fun getTransactions( -// spaceId: String, -// filter: TransactionService.TransactionsFilter, -// sortBy: String, -// sortDirection: String -// ): List { -// val allowedSortFields = setOf("date", "amount", "category.name", "createdAt") -// require(sortBy in allowedSortFields) { "Invalid sort field: $sortBy" } -// -// val direction = when (sortDirection.uppercase()) { -// "ASC" -> Direction.ASC -// "DESC" -> Direction.DESC -// else -> throw IllegalArgumentException("Sort direction must be 'ASC' or 'DESC'") -// } -// val basicAggregation = basicAggregation(spaceId) -// -// val sort = sort(Sort.by(direction, sortBy)) -// val matchCriteria = mutableListOf() -// filter.dateFrom?.let { matchCriteria.add(Criteria.where("date").gte(it)) } -// filter.dateTo?.let { matchCriteria.add(Criteria.where("date").lte(it)) } -// val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray())) -// val aggregation = -// newAggregation( -// matchStage, -// *basicAggregation.toTypedArray(), -// sort -// ) -// -// return mongoTemplate.aggregate(aggregation, "transactions", Transaction::class.java) -// .collectList() -// .awaitSingle() -// } -// -// override suspend fun getTransaction( -// spaceId: String, -// transactionId: String -// ): Transaction { -// val matchCriteria = mutableListOf() -// matchCriteria.add(Criteria.where("spaceId").`is`(spaceId)) -// matchCriteria.add(Criteria.where("_id").`is`(ObjectId(transactionId))) -// val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray())) -// -// val aggregation = -// newAggregation( -// matchStage, -// ) -// return mongoTemplate.aggregate(aggregation, "transactions", Transaction::class.java) -// .awaitFirstOrNull() ?: throw NotFoundException("Transaction with ID $transactionId not found") -// } -// -// override suspend fun createTransaction( -// spaceId: String, -// transaction: TransactionDTO.CreateTransactionDTO -// ): Transaction { -// if (transaction.type == Transaction.TransactionType.TRANSFER && transaction.toAccountId == null) { -// throw IllegalArgumentException("Cannot create a transaction with type TRANSFER without a toAccountId") -// } -// val category = categoryService.getCategory(spaceId, transaction.categoryId) -// if (transaction.type != Transaction.TransactionType.TRANSFER && transaction.type.name != category.type.name) { -// throw IllegalArgumentException("Transaction type should match with category type") -// } -// val transaction = Transaction( -// spaceId = spaceId, -// type = transaction.type, -// kind = transaction.kind, -// categoryId = transaction.categoryId, -// comment = transaction.comment, -// amount = transaction.amount, -// fees = transaction.fees, -// date = transaction.date, -// fromAccountId = transaction.fromAccountId, -// toAccountId = transaction.toAccountId, -// ) -// return transactionRepo.save(transaction).awaitSingle() -// } -// -// override suspend fun updateTransaction( -// spaceId: String, -// transaction: TransactionDTO.UpdateTransactionDTO -// ): Transaction { -// if (transaction.type == Transaction.TransactionType.TRANSFER && transaction.toAccountId == null) { -// throw IllegalArgumentException("Cannot edit a transaction with type TRANSFER without a toAccountId") -// } -// val exitingTx = getTransaction(spaceId, transaction.id) -// val transaction = exitingTx.copy( -// spaceId = exitingTx.spaceId, -// type = transaction.type, -// kind = transaction.kind, -// categoryId = transaction.category, -// comment = transaction.comment, -// amount = transaction.amount, -// fees = transaction.fees, -// date = transaction.date, -// fromAccountId = transaction.fromAccountId, -// toAccountId = transaction.toAccountId, -// ) -// return transactionRepo.save(transaction).awaitSingle() -// } -// -// override suspend fun deleteTransaction(spaceId: String, transactionId: String) { -// val transaction = getTransaction(spaceId, transactionId) -// transaction.isDeleted = true -// transactionRepo.save(transaction).awaitSingle() -// } -// -// -//} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/gpt/CategorizeService.kt b/src/main/kotlin/space/luminic/finance/services/gpt/CategorizeService.kt index 676a4dc..fcd3b3b 100644 --- a/src/main/kotlin/space/luminic/finance/services/gpt/CategorizeService.kt +++ b/src/main/kotlin/space/luminic/finance/services/gpt/CategorizeService.kt @@ -4,7 +4,6 @@ import com.github.kotlintelegrambot.Bot import com.github.kotlintelegrambot.entities.ChatId import com.github.kotlintelegrambot.entities.InlineKeyboardMarkup import com.github.kotlintelegrambot.entities.Message -import com.github.kotlintelegrambot.entities.MessageId import com.github.kotlintelegrambot.entities.ParseMode import com.github.kotlintelegrambot.entities.keyboard.InlineKeyboardButton import com.github.kotlintelegrambot.entities.keyboard.WebAppInfo @@ -12,10 +11,7 @@ import com.github.kotlintelegrambot.entities.reaction.ReactionType import com.github.kotlintelegrambot.types.TelegramBotResult import org.springframework.beans.factory.annotation.Qualifier import org.springframework.beans.factory.annotation.Value -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate -import org.springframework.stereotype.Repository import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional import space.luminic.finance.models.Transaction import space.luminic.finance.repos.CategoryRepo import space.luminic.finance.repos.TransactionRepo @@ -46,7 +42,7 @@ class CategorizeService( tx, categoriesRepo.findBySpaceId(job.spaceId) ) // тут твой вызов GPT - var unsuccessMessage: TelegramBotResult? = null + var message: TelegramBotResult? = null if (res.categoryId == 0) { if (tx.tgChatId != null && tx.tgMessageId != null) { @@ -56,7 +52,7 @@ class CategorizeService( listOf(ReactionType.Emoji("💔")), isBig = false ) - unsuccessMessage = bot.sendMessage( + message = bot.sendMessage( ChatId.fromId(tx.tgChatId), replyToMessageId = tx.tgMessageId, text = "К сожалению, мы не смогли распознать категорию.\n\nПопробуйте выставить ее самостоятельно.", @@ -81,7 +77,7 @@ class CategorizeService( ) val category = categoriesRepo.findBySpaceIdAndId(job.spaceId, res.categoryId) if (category != null) { - bot.sendMessage( + message = bot.sendMessage( ChatId.fromId(tx.tgChatId), replyToMessageId = tx.tgMessageId, text = "Определили категорию: ${category.name}.\n\nЕсли это не так, исправьте это в WebApp.", @@ -98,11 +94,11 @@ class CategorizeService( } } } - if (unsuccessMessage != null) { + if (message != null) { categoryJobRepo.successJob( job.id, - unsuccessMessage.get().chat.id, - unsuccessMessage.get().messageId + message.get().chat.id, + message.get().messageId ) } else { categoryJobRepo.successJob(job.id) diff --git a/src/main/kotlin/space/luminic/finance/services/gpt/DeepSeekCategorizationService.kt b/src/main/kotlin/space/luminic/finance/services/gpt/DeepSeekCategorizationService.kt index cd98673..13dc4f6 100644 --- a/src/main/kotlin/space/luminic/finance/services/gpt/DeepSeekCategorizationService.kt +++ b/src/main/kotlin/space/luminic/finance/services/gpt/DeepSeekCategorizationService.kt @@ -35,8 +35,8 @@ class DeepSeekCategorizationService( Задача: 1. Определите наиболее подходящую категорию из списка выше для транзакции пользователя. - 2. Верните ответ в формате: "ID категории", например "3". - 3. Если ни одна категория из списка не подходит, верните: "0". + 2. Верните ответ в формате: ID категории", например 3. + 3. Если ни одна категория из списка не подходит, верните: 0. Ответ должен быть кратким, одной строкой, без дополнительных пояснений. """.trimIndent() diff --git a/src/main/kotlin/space/luminic/finance/services/telegram/BotService.kt b/src/main/kotlin/space/luminic/finance/services/telegram/BotService.kt index 41e8070..4545b8d 100644 --- a/src/main/kotlin/space/luminic/finance/services/telegram/BotService.kt +++ b/src/main/kotlin/space/luminic/finance/services/telegram/BotService.kt @@ -119,7 +119,7 @@ class BotService( @Bean fun bot(): Bot { val bot = com.github.kotlintelegrambot.bot { - logLevel = LogLevel.None + logLevel = LogLevel.All() token = botToken dispatch { message(Filter.Text) { diff --git a/src/main/kotlin/space/luminic/finance/services/telegram/TransactionsServiceImpl.kt b/src/main/kotlin/space/luminic/finance/services/telegram/TransactionsServiceImpl.kt index c726e5f..66dedbd 100644 --- a/src/main/kotlin/space/luminic/finance/services/telegram/TransactionsServiceImpl.kt +++ b/src/main/kotlin/space/luminic/finance/services/telegram/TransactionsServiceImpl.kt @@ -35,7 +35,6 @@ class TransactionsServiceImpl( tgChatId = chatId, tgMessageId = messageId, ) - print(transaction) return transactionRepo.create(transaction, userId) }