mirror of
https://github.com/mvvasilev/personal-finances.git
synced 2025-04-19 14:19:52 +03:00
Clear transaction categories on re-apply of categorization rules
This commit is contained in:
parent
76a658986b
commit
3653dc5861
6 changed files with 31 additions and 67 deletions
|
@ -0,0 +1,10 @@
|
||||||
|
package dev.mvvasilev.finances.controllers;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController("/statistics")
|
||||||
|
public class StatisticsController {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()))
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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} />;
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue