Skip to content

Commit

Permalink
re-execution of "apply rules" now ignores unmatched entries (and keep…
Browse files Browse the repository at this point in the history
…s their bookings if there are any) #62
  • Loading branch information
mathisdt committed Feb 2, 2025
1 parent f376e0e commit 3de923a
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ public boolean hasBuchungenForWholeSum() {
BigDecimal::compareTo) == 0;
}

public RuleResult withBuchung(Buchung buchung) {
return new RuleResult(input, buchung);
}

public void fillGeneralData() {
for (Buchung buchung : result) {
// fill general data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,65 +23,99 @@
@RequiredArgsConstructor
@Slf4j
public class RuleService {
private static class LogWrapper {
private final StringBuilder memory = new StringBuilder();

@SuppressWarnings("unused") // used in Groovy script
public void log(String msg, Object[] args) {
log.info(msg, args);
if (!memory.isEmpty()) {
memory.append("\n");
}
memory.append(MessageFormatter.arrayFormat(msg, args).getMessage());
}

public String getComplete() {
return memory.toString();
}
}

private final PersistenceService persistenceService;

public RulesResult apply(SourceFile input) {
String rules = persistenceService.getRules();
List<Table> tables = persistenceService.getTables();

Binding sharedData = new Binding();
GroovyShell shell = new GroovyShell(sharedData);
Script parsed = shell.parse("import org.zephyrsoft.optigemspoonfeeder.model.*\n"
+ "def log(String msg, Object... args) {\n"
+ " logWrapper.log(msg, args)\n"
+ "}\n"
+ "static def buchung(Object hauptkonto, Object unterkonto = null, Object projekt = null, Object buchungstext = null) {\n"
+ " return new Buchung(hauptkonto, unterkonto, projekt, buchungstext)\n"
+ "}\n"
+ rules);

for (Table table : tables) {
sharedData.setProperty(table.getName(), table);
}

List<RuleResult> result = new ArrayList<>();
LogWrapper logWrapper = new LogWrapper();
sharedData.setProperty("logWrapper", logWrapper);
for (SourceEntry entry : input.getEntries()) {
sharedData.setProperty("eigenkonto", new SearchableString(entry.getKontobezeichnung()));
sharedData.setProperty("datum", entry.getValutaDatum());
sharedData.setProperty("soll", entry.isDebit());
sharedData.setProperty("haben", entry.isCredit());
sharedData.setProperty("betrag", entry.getBetrag());
sharedData.setProperty("buchungstext", new SearchableString(entry.getBuchungstext()));
sharedData.setProperty("verwendungszweck", new SearchableString(entry.getVerwendungszweckClean()));
sharedData.setProperty("bank", new SearchableString(entry.getBankKennung()));
sharedData.setProperty("konto", new SearchableString(entry.getKontoNummer()));
sharedData.setProperty("name", new SearchableString(entry.getName()));

Buchung booking = (Buchung) parsed.run();

RuleResult ruleResult = new RuleResult(entry, booking);
ruleResult.fillGeneralData();
result.add(ruleResult);
}
return new RulesResult(result, logWrapper.getComplete());
}
private static class LogWrapper {
private final StringBuilder memory = new StringBuilder();

@SuppressWarnings("unused") // used in Groovy script
public void log(String msg, Object[] args) {
log.info(msg, args);
if (!memory.isEmpty()) {
memory.append("\n");
}
memory.append(MessageFormatter.arrayFormat(msg, args).getMessage());
}

public String getComplete() {
return memory.toString();
}
}

private final PersistenceService persistenceService;

public RulesResult apply(RulesResult previousResults) {
Binding sharedData = new Binding();
Script parsed = createScript(sharedData);

LogWrapper logWrapper = new LogWrapper();
sharedData.setProperty("logWrapper", logWrapper);
List<RuleResult> result = new ArrayList<>();
for (RuleResult previousResult : previousResults.getResults()) {

insertEntryProperties(previousResult.getInput(), sharedData);

Buchung booking = (Buchung) parsed.run();

if (booking != null) {
RuleResult ruleResult = previousResult.withBuchung(booking);
ruleResult.fillGeneralData();
result.add(ruleResult);
} else {
result.add(previousResult);
}
}
return new RulesResult(result, logWrapper.getComplete());
}

public RulesResult apply(SourceFile input) {
Binding sharedData = new Binding();
Script parsed = createScript(sharedData);

LogWrapper logWrapper = new LogWrapper();
sharedData.setProperty("logWrapper", logWrapper);
List<RuleResult> result = new ArrayList<>();
for (SourceEntry entry : input.getEntries()) {
insertEntryProperties(entry, sharedData);

Buchung booking = (Buchung) parsed.run();

RuleResult ruleResult = new RuleResult(entry, booking);
ruleResult.fillGeneralData();
result.add(ruleResult);
}
return new RulesResult(result, logWrapper.getComplete());
}

private static void insertEntryProperties(final SourceEntry entry, final Binding sharedData) {
sharedData.setProperty("eigenkonto", new SearchableString(entry.getKontobezeichnung()));
sharedData.setProperty("datum", entry.getValutaDatum());
sharedData.setProperty("soll", entry.isDebit());
sharedData.setProperty("haben", entry.isCredit());
sharedData.setProperty("betrag", entry.getBetrag());
sharedData.setProperty("buchungstext", new SearchableString(entry.getBuchungstext()));
sharedData.setProperty("verwendungszweck", new SearchableString(entry.getVerwendungszweckClean()));
sharedData.setProperty("bank", new SearchableString(entry.getBankKennung()));
sharedData.setProperty("konto", new SearchableString(entry.getKontoNummer()));
sharedData.setProperty("name", new SearchableString(entry.getName()));
}

private Script createScript(final Binding sharedData) {
String rules = persistenceService.getRules();
List<Table> tables = persistenceService.getTables();

GroovyShell shell = new GroovyShell(sharedData);
Script parsed = shell.parse("""
import org.zephyrsoft.optigemspoonfeeder.model.*
def log(String msg, Object... args) {
logWrapper.log(msg, args)
}
static def buchung(Object hauptkonto, Object unterkonto = null, Object projekt = null, Object buchungstext = null) {
return new Buchung(hauptkonto, unterkonto, projekt, buchungstext)
}
""" + rules);

for (Table table : tables) {
sharedData.setProperty(table.getName(), table);
}
return parsed;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,11 @@ private void parseUploadedFile(InputStream inputStream, String filename) {

private void convertParsedData() {
try {
result = ruleService.apply(parsed);
if (result != null) {
result = ruleService.apply(result);
} else {
result = ruleService.apply(parsed);
}
logArea.setText(result.getLogMessages());
updateFooter();
save.setEnabled(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.zephyrsoft.optigemspoonfeeder.model.Buchung;
import org.zephyrsoft.optigemspoonfeeder.model.RuleResult;
import org.zephyrsoft.optigemspoonfeeder.model.RulesResult;
import org.zephyrsoft.optigemspoonfeeder.source.SourceFile;
Expand Down Expand Up @@ -56,6 +57,44 @@ void apply() throws Exception {
assertThat(rulesResult)
.areExactly(1, new Condition<>(rr -> rr.getResult().isEmpty()
&& rr.getInput().getVerwendungszweck().contains("Einzahlung Bar"), ""));

// modify the data for re-application of the rules
RuleResult noBooking = rulesResult.stream()
.filter(rr -> rr.getResult().isEmpty())
.findAny()
.orElseThrow();
noBooking.getResult().add(new Buchung(8600, 0, 0, "Einzahlung BAR"));

RuleResult bareinzahlungBooking = rulesResult.stream()
.filter(rr -> rr.getResult().getFirst().getBuchungstext().equals("Bareinzahlung"))
.findAny()
.orElseThrow();
bareinzahlungBooking.clearBuchungen();

// re-apply rules
RulesResult result2 = service.apply(result);
rulesResult = result2.getResults();
assertNotNull(rulesResult);
assertEquals(8, rulesResult.size());

// matched:
assertThat(rulesResult)
.areExactly(1, matches(4940, 0, 0, "Telekom"));
assertThat(rulesResult)
.areExactly(1, matches(8010, 2, 0, "Vorname Test 2 Nachname Test 2"));
assertThat(rulesResult)
.areExactly(1, matches(8010, 1, 1, "Vorname Test 1 Nachname Test 1"));
assertThat(rulesResult)
.areExactly(1, matches(8205, 4, 21, "GFYC-Freizeit"));
assertThat(rulesResult)
.areExactly(1, matches(8205, 5, 20, "Roots-Freizeit"));
assertThat(rulesResult)
.areExactly(1, matches(8205, 5, 20, "Freizeit Junge Erwachsene"));
assertThat(rulesResult)
.areExactly(1, matches(1360, 0, 0, "Bareinzahlung"));

// unmatched by the rules, but manually edited previously:
assertThat(rulesResult).areExactly(1, matches(8600, 0, 0, "Einzahlung BAR"));
}
}

Expand Down

0 comments on commit 3de923a

Please sign in to comment.