Skip to content

Commit 9283c66

Browse files
committed
#178: refactor logic and add tests for conversion for UI. improve ui.
1 parent f0bd7af commit 9283c66

File tree

8 files changed

+618
-161
lines changed

8 files changed

+618
-161
lines changed

src/main/java/de/doubleslash/keeptime/common/Resources.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ public enum RESOURCE {
7171

7272
SVG_MULTIPLE_CLIPBOARD_ICON("/svgs/copy.svg"),
7373

74+
SVG_SPINNER_SOLID("/svgs/spinner-solid.svg"),
75+
7476
ICON_MAIN("/icons/icon.png")
7577

7678
;
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
package de.doubleslash.keeptime.controller;
2+
3+
import de.doubleslash.keeptime.model.ExternalProjectMapping;
4+
import de.doubleslash.keeptime.model.ExternalSystem;
5+
import de.doubleslash.keeptime.model.Project;
6+
import de.doubleslash.keeptime.model.Work;
7+
import de.doubleslash.keeptime.model.repos.ExternalProjectsMappingsRepository;
8+
import de.doubleslash.keeptime.model.settings.HeimatSettings;
9+
import de.doubleslash.keeptime.rest.integration.heimat.HeimatAPI;
10+
import de.doubleslash.keeptime.rest.integration.heimat.model.HeimatTask;
11+
import de.doubleslash.keeptime.rest.integration.heimat.model.HeimatTime;
12+
import de.doubleslash.keeptime.view.ProjectReport;
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
import org.springframework.beans.factory.annotation.Autowired;
16+
import org.springframework.stereotype.Service;
17+
18+
import java.time.LocalDate;
19+
import java.util.*;
20+
import java.util.stream.Collectors;
21+
22+
@Service
23+
public class HeimatController {
24+
private static final Logger LOG = LoggerFactory.getLogger(HeimatController.class);
25+
26+
private final Controller controller;
27+
28+
private final ExternalProjectsMappingsRepository externalProjectsMappingsRepository;
29+
private final HeimatAPI heimatAPI;
30+
31+
@Autowired
32+
public HeimatController(HeimatSettings heimatSettings,
33+
ExternalProjectsMappingsRepository externalProjectsMappingsRepository, final Controller controller) {
34+
this.controller = controller;
35+
this.externalProjectsMappingsRepository = externalProjectsMappingsRepository;
36+
heimatAPI = new HeimatAPI(heimatSettings.getHeimatUrl(), heimatSettings.getHeimatPat());
37+
}
38+
39+
public HeimatController(HeimatAPI heimatAPI, ExternalProjectsMappingsRepository externalProjectsMappingsRepository,
40+
final Controller controller) {
41+
this.controller = controller;
42+
this.externalProjectsMappingsRepository = externalProjectsMappingsRepository;
43+
this.heimatAPI = heimatAPI;
44+
}
45+
46+
public List<Mapping> getTableRows(final LocalDate currentReportDate, final List<Work> currentWorkItems) {
47+
// TODO check if external projects are available for the currentDay
48+
final List<HeimatTask> heimatTasks = heimatAPI.getMyTasks(currentReportDate);
49+
50+
final List<HeimatTime> heimatTimes = heimatAPI.getMyTimes(currentReportDate);
51+
final List<ExternalProjectMapping> mappedProjects = externalProjectsMappingsRepository.findByExternalSystemId(
52+
ExternalSystem.Heimat);
53+
54+
final List<Mapping> list = new ArrayList<>();
55+
56+
final SortedSet<Project> workedProjectsSet = currentWorkItems.stream()
57+
.map(Work::getProject)
58+
.filter(Project::isWork)
59+
.collect(Collectors.toCollection(() -> new TreeSet<>(
60+
Comparator.comparing(Project::getIndex))));
61+
62+
for (final Project project : workedProjectsSet) {
63+
String heimatNotes = "";
64+
long heimatTimeSeconds = 0;
65+
boolean isMappedInHeimat = false;
66+
final Optional<ExternalProjectMapping> heimatMappings = mappedProjects.stream()
67+
.filter(mp -> mp.getProject().getId()
68+
== project.getId())
69+
.findAny();
70+
List<HeimatTime> optionalAlreadyBookedTimes = new ArrayList<>();
71+
Optional<Mapping> optionalExistingMapping = Optional.empty();
72+
if (heimatMappings.isPresent()) {
73+
isMappedInHeimat = true;
74+
optionalExistingMapping = list.stream()
75+
.filter(mapping -> mapping.heimatTaskId == heimatMappings.get()
76+
.getExternalTaskId())
77+
.findAny();
78+
optionalAlreadyBookedTimes = heimatTimes.stream()
79+
.filter(heimatTime -> heimatMappings.stream()
80+
.anyMatch(
81+
hm -> heimatTime.taskId()
82+
== hm.getExternalTaskId()))
83+
.toList();
84+
if (!optionalAlreadyBookedTimes.isEmpty()) {
85+
heimatNotes = optionalAlreadyBookedTimes.stream()
86+
.map(HeimatTime::note)
87+
.collect(Collectors.joining(". "));
88+
heimatTimeSeconds = optionalAlreadyBookedTimes.stream()
89+
.reduce(0L, (subtotal, element) -> subtotal
90+
+ element.durationInMinutes() * 60L, Long::sum);
91+
}
92+
}
93+
final List<Work> onlyCurrentProjectWork = currentWorkItems.stream()
94+
.filter(w -> w.getProject() == project)
95+
.toList();
96+
97+
final long projectWorkSeconds = controller.calcSeconds(onlyCurrentProjectWork);
98+
99+
final ProjectReport pr = new ProjectReport();
100+
for (final Work work : onlyCurrentProjectWork) {
101+
final String currentWorkNote = work.getNotes();
102+
pr.appendToWorkNotes(currentWorkNote);
103+
}
104+
final String keeptimeNotes = pr.getNotes();
105+
String canBeSynced;
106+
if (!isMappedInHeimat) {
107+
canBeSynced = "Not mapped in Heimat";
108+
} else if (heimatTasks.stream().noneMatch(ht -> ht.id() == heimatMappings.get().getExternalTaskId())) {
109+
canBeSynced = "Heimat Task is no longer available.";
110+
isMappedInHeimat = false;
111+
} else {
112+
final ExternalProjectMapping externalProjectMapping = heimatMappings.get();
113+
canBeSynced = "Sync to " + externalProjectMapping.getExternalTaskName() + "("
114+
+ externalProjectMapping.getExternalProjectName() + ")";
115+
}
116+
117+
if (optionalExistingMapping.isPresent()) {
118+
final Mapping existingMapping = optionalExistingMapping.get();
119+
final ArrayList<Project> projects = new ArrayList<>(existingMapping.projects());
120+
projects.add(project);
121+
final Mapping mapping = new Mapping(isMappedInHeimat ? heimatMappings.get().getExternalTaskId() : -1,
122+
isMappedInHeimat, canBeSynced, existingMapping.existingTimes(), projects,
123+
existingMapping.heimatNotes(),
124+
existingMapping.keeptimeNotes() + ". " + keeptimeNotes,
125+
existingMapping.heimatSeconds(),
126+
existingMapping.keeptimeSeconds() + projectWorkSeconds);
127+
list.remove(existingMapping);
128+
list.add(mapping);
129+
} else {
130+
final List<Project> projects = Collections.singletonList(project);
131+
final Mapping mapping = new Mapping(isMappedInHeimat ? heimatMappings.get().getExternalTaskId() : -1,
132+
isMappedInHeimat, canBeSynced, optionalAlreadyBookedTimes, projects, heimatNotes, keeptimeNotes,
133+
heimatTimeSeconds, projectWorkSeconds);
134+
list.add(mapping);
135+
}
136+
}
137+
138+
return list;
139+
}
140+
141+
public List<HeimatErrors> saveDay(final List<Asdf> items, LocalDate date) {
142+
List<HeimatErrors> errors = new ArrayList<>();
143+
144+
items.stream().filter(tr -> tr.shouldSync).forEach(item -> {
145+
if (item.userNotes.isEmpty()) {
146+
errors.add(new HeimatErrors(item, "No notes were given"));
147+
return;
148+
}
149+
final int durationInMinutes = item.userMinutes;
150+
if (durationInMinutes <= 0 || durationInMinutes % 15 != 0) {
151+
errors.add(new HeimatErrors(item, "Duration '"+durationInMinutes+"' is not valid for project"));
152+
return;
153+
}
154+
155+
final HeimatTime heimatTime = new HeimatTime(item.mapping.heimatTaskId, date, null, null, durationInMinutes,
156+
item.userNotes, 0L);
157+
158+
try {
159+
item.mapping.existingTimes().forEach(existingTime -> {
160+
LOG.info("Removing existing booked time '{}'", existingTime);
161+
heimatAPI.deleteMyTime(existingTime.id());
162+
});
163+
LOG.info("Adding new time time '{}'", heimatTime);
164+
heimatAPI.addMyTime(heimatTime);
165+
} catch (Exception e) {
166+
LOG.error("Error while persisting time '{}'", heimatTime, e);
167+
errors.add(new HeimatErrors(item, "Error while persisting." + e.getMessage()));
168+
}
169+
});
170+
171+
return errors;
172+
}
173+
174+
public static class Asdf {
175+
private final Mapping mapping;
176+
private boolean shouldSync;
177+
private String userNotes;
178+
private int userMinutes;
179+
180+
public Asdf(Mapping mapping, boolean shouldSync, String userNotes, int userMinutes) {
181+
this.mapping = mapping;
182+
this.shouldSync = shouldSync;
183+
this.userNotes = userNotes;
184+
this.userMinutes = userMinutes;
185+
}
186+
187+
public void setShouldSync(final boolean shouldSync) {
188+
this.shouldSync = shouldSync;
189+
}
190+
191+
public void setUserNotes(final String userNotes) {
192+
this.userNotes = userNotes;
193+
}
194+
195+
public void setUserMinutes(final int userMinutes) {
196+
this.userMinutes = userMinutes;
197+
}
198+
199+
public Mapping getMapping() {
200+
return mapping;
201+
}
202+
203+
public boolean isShouldSync() {
204+
return shouldSync;
205+
}
206+
207+
public String getUserNotes() {
208+
return userNotes;
209+
}
210+
211+
public int getUserMinutes() {
212+
return userMinutes;
213+
}
214+
}
215+
216+
public record Mapping(long heimatTaskId, boolean canBeSynced, String syncMessage, List<HeimatTime> existingTimes,
217+
List<Project> projects, String heimatNotes, String keeptimeNotes, long heimatSeconds,
218+
long keeptimeSeconds) {}
219+
220+
public record HeimatErrors (
221+
Asdf mapping, String errorMessage
222+
){}
223+
}

0 commit comments

Comments
 (0)