3 Commits

Author SHA1 Message Date
Cedric 9a109b4709 Merge pull request 'Dev' (#18) from dev into main
Build and Deploy Versioned Spring Boot Server / build (push) Successful in 1m25s
Reviewed-on: #18
2026-07-07 00:35:22 +02:00
Cedric 003cf7e2da Merge pull request 'Fix Tag Workflow' (#17) from dev into main
Build and Deploy Versioned Spring Boot Server / build (push) Successful in 1m16s
Reviewed-on: #17
2026-07-05 00:08:49 +02:00
Cedric c1c530c46b Merge pull request 'Release 1.1.0' (#16) from dev into main
Reviewed-on: #16
2026-07-05 00:03:46 +02:00
12 changed files with 0 additions and 155 deletions
@@ -98,18 +98,6 @@ public class ExpenseListController {
return new ResponseEntity<>(renamed, HttpStatus.OK);
}
@PutMapping("/{id}/default-split")
public ResponseEntity<ExpenseList> updateDefaultSplit(@PathVariable("id") Long id,
@RequestBody @Valid DefaultSplitRequest request, Authentication authentication) {
AppUser user = authenticatedUserResolver.resolveCurrentUser(authentication);
Optional<ExpenseList> listOpt = expenseListService.findById(id);
if (listOpt.isEmpty())
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
assertMember(user, listOpt.get());
ExpenseList updated = expenseListService.updateDefaultSplit(id, request.getPercentage());
return new ResponseEntity<>(updated, HttpStatus.OK);
}
@PostMapping("/{id}/add")
public ResponseEntity<Expense> addExpenseToList(
@PathVariable("id") Long expenseListId,
@@ -1,17 +0,0 @@
package de.zendric.app.xpensely_server.model;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class DefaultSplitRequest {
@NotNull(message = "Percentage is required")
@Min(value = 0, message = "Percentage must be between 0 and 100")
@Max(value = 100, message = "Percentage must be between 0 and 100")
private Integer percentage;
}
@@ -6,8 +6,6 @@ import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@@ -38,12 +36,6 @@ public class Expense {
private Double personalUseAmount;
private Double otherPersonAmount;
private String category;
@Enumerated(EnumType.STRING)
private SplitMode splitMode;
private Integer splitPercentage;
private LocalDate date;
private LocalDateTime lastModified;
@@ -3,8 +3,6 @@ package de.zendric.app.xpensely_server.model;
import java.time.LocalDate;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@@ -35,12 +33,6 @@ public class ExpenseChangeRequest {
private Double personalUseAmount;
private Double otherPersonAmount;
private SplitMode splitMode;
@Min(0)
@Max(100)
private Integer splitPercentage;
@NotNull(message = "Date is required")
private LocalDate date;
@@ -63,8 +55,6 @@ public class ExpenseChangeRequest {
// The edit dialog sends "" for a missing category; store null so the
// stub state stays uniform and history diffs don't report null -> "".
expense.setCategory(category == null || category.isBlank() ? null : category);
expense.setSplitMode(splitMode == null ? SplitMode.DETAILED : splitMode);
expense.setSplitPercentage(splitPercentage);
return expense;
}
@@ -3,8 +3,6 @@ package de.zendric.app.xpensely_server.model;
import java.time.LocalDate;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@@ -35,12 +33,6 @@ public class ExpenseInput {
private Double personalUseAmount;
private Double otherPersonAmount;
private SplitMode splitMode;
@Min(0)
@Max(100)
private Integer splitPercentage;
@NotNull(message = "Date is required")
private LocalDate date;
@@ -64,8 +56,6 @@ public class ExpenseInput {
expense.setOwner(appUser);
expense.setTitle(title);
expense.setCategory(category);
expense.setSplitMode(splitMode == null ? SplitMode.DETAILED : splitMode);
expense.setSplitPercentage(splitPercentage);
return expense;
}
@@ -31,8 +31,6 @@ public class ExpenseList {
private String name;
private Integer defaultSplitPercentage = 50;
private String inviteCode;
@JsonIgnore
private LocalDateTime inviteCodeExpiration;
@@ -1,6 +0,0 @@
package de.zendric.app.xpensely_server.model;
public enum SplitMode {
DETAILED,
PERCENTAGE
}
@@ -50,13 +50,6 @@ public class ExpenseListService {
return repository.save(list);
}
public ExpenseList updateDefaultSplit(Long id, int percentage) {
ExpenseList list = repository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("ExpenseList not found with id: " + id));
list.setDefaultSplitPercentage(percentage);
return repository.save(list);
}
public Optional<ExpenseList> findById(Long id) {
return repository.findById(id);
}
@@ -144,8 +137,6 @@ public class ExpenseListService {
existingExpense.setDate(updatedExpense.getDate());
existingExpense.setOwner(updatedExpense.getOwner());
existingExpense.setCategory(updatedExpense.getCategory());
existingExpense.setSplitMode(updatedExpense.getSplitMode());
existingExpense.setSplitPercentage(updatedExpense.getSplitPercentage());
existingExpense.setLastModified(LocalDateTime.now());
Expense saved = expenseRepository.save(existingExpense);
@@ -162,8 +153,6 @@ public class ExpenseListService {
copy.setPersonalUseAmount(e.getPersonalUseAmount());
copy.setOtherPersonAmount(e.getOtherPersonAmount());
copy.setCategory(e.getCategory());
copy.setSplitMode(e.getSplitMode());
copy.setSplitPercentage(e.getSplitPercentage());
copy.setDate(e.getDate());
copy.setOwner(e.getOwner());
return copy;
@@ -69,10 +69,6 @@ public class HistoryService {
addIfChanged(changes, "personalUseAmount", before.getPersonalUseAmount(), after.getPersonalUseAmount());
addIfChanged(changes, "otherPersonAmount", before.getOtherPersonAmount(), after.getOtherPersonAmount());
addIfChanged(changes, "category", before.getCategory(), after.getCategory());
addIfChanged(changes, "splitMode",
before.getSplitMode() == null ? null : before.getSplitMode().name(),
after.getSplitMode() == null ? null : after.getSplitMode().name());
addIfChanged(changes, "splitPercentage", before.getSplitPercentage(), after.getSplitPercentage());
addIfChanged(changes, "date", before.getDate(), after.getDate());
addIfChanged(changes, "owner",
before.getOwner() == null ? null : before.getOwner().getUsername(),
@@ -314,40 +314,6 @@ class ExpenseListControllerTest {
.andExpect(status().isNotFound());
}
// --- Default split ---
@Test
void updateDefaultSplit_setsPercentageForMember() throws Exception {
AppUser owner = new AppUser(); owner.setId(1L);
ExpenseList list = new ExpenseList(); list.setId(5L); list.setOwner(owner);
when(expenseListService.findById(5L)).thenReturn(Optional.of(list));
when(authenticatedUserResolver.resolveCurrentUser(any())).thenReturn(owner);
when(expenseListService.updateDefaultSplit(5L, 60)).thenReturn(list);
mockMvc.perform(put("/api/expenselist/5/default-split")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"percentage\":60}"))
.andExpect(status().isOk());
org.mockito.Mockito.verify(expenseListService).updateDefaultSplit(5L, 60);
}
@Test
void updateDefaultSplit_nonMember_returns403() throws Exception {
AppUser owner = new AppUser(); owner.setId(1L);
AppUser stranger = new AppUser(); stranger.setId(2L);
ExpenseList list = new ExpenseList(); list.setId(5L); list.setOwner(owner);
when(expenseListService.findById(5L)).thenReturn(Optional.of(list));
when(authenticatedUserResolver.resolveCurrentUser(any())).thenReturn(stranger);
mockMvc.perform(put("/api/expenselist/5/default-split")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"percentage\":60}"))
.andExpect(status().isForbidden());
}
// --- Duplicate-username invite guard ---
@Test
@@ -3,7 +3,6 @@ package de.zendric.app.xpensely_Server.services;
import de.zendric.app.xpensely_server.model.AppUser;
import de.zendric.app.xpensely_server.model.Expense;
import de.zendric.app.xpensely_server.model.ExpenseList;
import de.zendric.app.xpensely_server.model.SplitMode;
import de.zendric.app.xpensely_server.repo.ExpenseListRepository;
import de.zendric.app.xpensely_server.repo.ExpenseRepository;
import de.zendric.app.xpensely_server.repo.XpenselyCustomCategoryRepository;
@@ -110,29 +109,6 @@ class ExpenseListServiceTest {
assertThat(beforeCaptor.getValue().getAmount()).isEqualTo(12.0);
}
@Test
void updateExpense_persistsSplitModeAndPercentage() {
AppUser actor = new AppUser(); actor.setId(1L);
ExpenseList list = new ExpenseList(); list.setId(10L); list.setOwner(actor);
Expense existing = makeExpense(5L, "Groceries", 100.0);
list.setExpenses(new ArrayList<>(List.of(existing)));
Expense incoming = makeExpense(5L, "Groceries", 100.0);
incoming.setDate(LocalDate.now());
incoming.setOwner(list.getOwner());
incoming.setSplitMode(SplitMode.PERCENTAGE);
incoming.setSplitPercentage(70);
when(repository.findById(10L)).thenReturn(Optional.of(list));
when(expenseRepository.findById(5L)).thenReturn(Optional.of(existing));
when(expenseRepository.save(any(Expense.class))).thenAnswer(inv -> inv.getArgument(0));
Expense saved = service.updateExpense(10L, incoming, actor);
assertThat(saved.getSplitMode()).isEqualTo(SplitMode.PERCENTAGE);
assertThat(saved.getSplitPercentage()).isEqualTo(70);
}
@Test
void deleteById_deletesHistoryEntriesBeforeDeletingList() {
service.deleteById(10L);
@@ -24,7 +24,6 @@ import de.zendric.app.xpensely_server.model.ExpenseHistoryEntry;
import de.zendric.app.xpensely_server.model.ExpenseList;
import de.zendric.app.xpensely_server.model.FieldChange;
import de.zendric.app.xpensely_server.model.HistoryEntryType;
import de.zendric.app.xpensely_server.model.SplitMode;
import de.zendric.app.xpensely_server.repo.ExpenseHistoryRepository;
import de.zendric.app.xpensely_server.services.HistoryService;
@@ -87,22 +86,6 @@ class HistoryServiceTest {
assertThat(changes).containsExactly(new FieldChange("owner", "alice", "ben"));
}
@Test
void diff_reportsSplitModeAndPercentageChanges() {
Expense before = new Expense();
before.setSplitMode(SplitMode.DETAILED);
before.setSplitPercentage(null);
Expense after = new Expense();
after.setSplitMode(SplitMode.PERCENTAGE);
after.setSplitPercentage(70);
List<FieldChange> changes = service.diff(before, after);
assertThat(changes).contains(new FieldChange("splitMode", "DETAILED", "PERCENTAGE"));
assertThat(changes).contains(new FieldChange("splitPercentage", null, "70"));
}
@Test
void diff_nullToValue_isReported() {
Expense before = expense("Taxi", 9.0, null, LocalDate.of(2026, 7, 1), user(1L, "alice"));