Clear transaction categories on re-apply of categorization rules

This commit is contained in:
Miroslav Vasilev 2023-12-30 18:59:44 +02:00
parent 76a658986b
commit 3653dc5861
6 changed files with 31 additions and 67 deletions

View file

@ -0,0 +1,10 @@
package dev.mvvasilev.finances.controllers;
import org.springframework.web.bind.annotation.RestController;
@RestController("/statistics")
public class StatisticsController {
}

View file

@ -3,8 +3,23 @@ package dev.mvvasilev.finances.persistence;
import dev.mvvasilev.finances.entity.ProcessedTransaction; import dev.mvvasilev.finances.entity.ProcessedTransaction;
import dev.mvvasilev.finances.entity.ProcessedTransactionCategory; import dev.mvvasilev.finances.entity.ProcessedTransactionCategory;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.stream.Stream;
@Repository @Repository
public interface ProcessedTransactionCategoryRepository extends JpaRepository<ProcessedTransactionCategory, Long> { public interface ProcessedTransactionCategoryRepository extends JpaRepository<ProcessedTransactionCategory, Long> {
@Query(
value="""
DELETE FROM categories.processed_transaction_category
WHERE processed_transaction_id IN (:transactionIds)
""",
nativeQuery = true
)
@Modifying
void deleteAllForTransactions(@Param("transactionIds") Collection<Long> transactionIds);
} }

View file

@ -80,6 +80,8 @@ public class CategoryService {
final var categorizations = categorizationRepository.fetchForUser(userId); final var categorizations = categorizationRepository.fetchForUser(userId);
final var transactions = processedTransactionRepository.fetchForUser(userId); final var transactions = processedTransactionRepository.fetchForUser(userId);
processedTransactionCategoryRepository.deleteAllForTransactions(transactions.stream().map(AbstractEntity::getId).toList());
// Run each category's rules for all transactions in parallel to eachother // Run each category's rules for all transactions in parallel to eachother
final var futures = categorizations.stream() final var futures = categorizations.stream()
.collect(Collectors.groupingBy(Categorization::getCategoryId, HashMap::new, Collectors.toList())) .collect(Collectors.groupingBy(Categorization::getCategoryId, HashMap::new, Collectors.toList()))

View file

@ -11,14 +11,12 @@ import Grid from "@mui/material/Unstable_Grid2";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Divider from "@mui/material/Divider"; import Divider from "@mui/material/Divider";
import { import {
Chip,
Dialog, Dialog,
DialogActions, DialogActions,
DialogContent, DialogContent,
DialogContentText, DialogContentText,
DialogTitle, DialogTitle,
Modal, Modal,
Stack,
TextField TextField
} from "@mui/material"; } from "@mui/material";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
@ -188,39 +186,6 @@ export default function CategoriesPage() {
}} }}
showDelete showDelete
/> />
{/*<Stack*/}
{/* sx={{*/}
{/* overflowY: "scroll"*/}
{/* }}*/}
{/* minHeight={"100px"}*/}
{/* maxHeight={"250px"}*/}
{/* useFlexGap*/}
{/* flexWrap="wrap"*/}
{/* direction={"row"}*/}
{/* spacing={1}*/}
{/*>*/}
{/* {*/}
{/* categories.map(c => {*/}
{/* let variant = (selectedCategory?.id ?? -1) === c.id ? "filled" : "outlined";*/}
{/* return (*/}
{/* <Chip*/}
{/* key={c.id}*/}
{/* onClick={(e) => {*/}
{/* setSelectedCategory({...c});*/}
{/* }}*/}
{/* onDelete={() => {*/}
{/* setSelectedCategory(c);*/}
{/* openConfirmDeleteCategoryModal(true);*/}
{/* }}*/}
{/* label={c.name}*/}
{/* deleteIcon={<Delete/>}*/}
{/* variant={variant}*/}
{/* />*/}
{/* );*/}
{/* })*/}
{/* }*/}
{/*</Stack>*/}
</Grid> </Grid>
<Grid xs={12} lg={12}> <Grid xs={12} lg={12}>

View file

@ -12,6 +12,8 @@ export default function CategoriesBox({
minHeight: minHeight = "100px", minHeight: minHeight = "100px",
maxHeight: maxHeight = "250px", maxHeight: maxHeight = "250px",
}) { }) {
let areChipsDeletable = onCategoryDelete !== undefined;
return ( return (
<Stack <Stack
sx={{ sx={{
@ -28,12 +30,11 @@ export default function CategoriesBox({
{ {
categories.map(c => { categories.map(c => {
let variant = selectable && (selected?.id ?? -1) === c.id ? "filled" : "outlined"; let variant = selectable && (selected?.id ?? -1) === c.id ? "filled" : "outlined";
let isDeletable = onCategoryDelete !== undefined;
return ( return (
<> <>
{ {
isDeletable && areChipsDeletable &&
<Chip <Chip
key={c.id} key={c.id}
onClick={(e) => onCategorySelect(e, c)} onClick={(e) => onCategorySelect(e, c)}
@ -44,7 +45,7 @@ export default function CategoriesBox({
/> />
} }
{ {
!isDeletable && !areChipsDeletable &&
<Chip <Chip
key={c.id} key={c.id}
onClick={(e) => onCategorySelect(e, c)} onClick={(e) => onCategorySelect(e, c)}

View file

@ -1,29 +0,0 @@
import {useRef, useEffect, useState} from "react";
import { Network } from "vis-network";
export default function VisNetwork({ nodes: nodes = [], edges: edges = [], backgroundColor: backgroundColor = "#ffffff", options: options = {} }) {
const visJsRef = useRef(null);
const [network, setNetwork] = useState();
useEffect(() => {
const network = visJsRef.current && new Network(visJsRef.current, { nodes, edges }, options);
network.on("beforeDrawing", function(ctx) {
// save current translate/zoom
ctx.save();
// reset transform to identity
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height)
// restore old transform
ctx.restore();
})
}, [visJsRef, backgroundColor, edges, nodes, options]);
return <div ref={visJsRef} />;
}