diff --git a/src/main/java/Constants.java b/src/main/java/Constants.java new file mode 100644 index 0000000..1ff8cda --- /dev/null +++ b/src/main/java/Constants.java @@ -0,0 +1,4 @@ +public class Constants { + + public static final Integer SUCCESS_CODE = 0; +} diff --git a/src/main/java/SplitwiseApplication.java b/src/main/java/SplitwiseApplication.java new file mode 100644 index 0000000..99bf6a4 --- /dev/null +++ b/src/main/java/SplitwiseApplication.java @@ -0,0 +1,6 @@ +public class SplitwiseApplication { + + public static void main(String[] args) { + + } +} diff --git a/src/main/java/handlers/EqualComputeHandler.java b/src/main/java/handlers/EqualComputeHandler.java new file mode 100644 index 0000000..9ab621a --- /dev/null +++ b/src/main/java/handlers/EqualComputeHandler.java @@ -0,0 +1,59 @@ +package handlers; + +import impl.UserServiceImpl; +import interfaces.ComputeHandler; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import models.SplitwiseAddRequest; +import models.User; + +import java.util.HashMap; +import java.util.List; + + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class EqualComputeHandler implements ComputeHandler { + + private UserServiceImpl userService; + + @Override + public void compute(SplitwiseAddRequest request, HashMap balancesList) { + List participants = userService.fetchUserByIds(request.getParticipants()); + compute(participants, request.getRequestAdderId(), request.getAmount().getValue(), balancesList); + } + + + private void compute(List participants, String requesterId, Double totalValue, HashMap balancesList) { + + Double equalShare = totalValue / participants.size(); + // For the rest of the users also the same has to be updated + participants + .forEach( + participant -> { + if (!participant.getUserId().equals(requesterId)) { + Double newBalance = computeParticipantsBalance(participant.getTotalBalance(), equalShare); + participant.setTotalBalance(newBalance); + balancesList.put(participant.getUserId(), newBalance); + } else { + Double updatedRequestorBalance = computeRequestorBalance(participant.getTotalBalance(), equalShare, participants.size()); + participant.setTotalBalance(updatedRequestorBalance); + balancesList.put(participant.getUserId(), updatedRequestorBalance); + } + } + ); + + } + + private Double computeRequestorBalance(Double balance, Double equalShare, Integer totalNumberOfParticipants) { + return (balance != null ? balance : 0.0) + equalShare * (totalNumberOfParticipants - 1); + } + + private Double computeParticipantsBalance(Double totalbalance, Double equalShare) { + return (totalbalance != null ? totalbalance : 0.0) + -1 * equalShare; + } +} diff --git a/src/main/java/handlers/ExactComputeHandler.java b/src/main/java/handlers/ExactComputeHandler.java new file mode 100644 index 0000000..c97b2aa --- /dev/null +++ b/src/main/java/handlers/ExactComputeHandler.java @@ -0,0 +1,54 @@ +package handlers; + +import impl.UserServiceImpl; +import interfaces.ComputeHandler; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import models.SplitwiseAddRequest; +import models.User; + +import java.util.HashMap; +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ExactComputeHandler implements ComputeHandler { + + private UserServiceImpl userService; + + + @Override + public void compute(SplitwiseAddRequest request, HashMap balancesList) { + List participants = userService.fetchUserByIds(request.getParticipants()); + compute(participants, request.getRequestAdderId(), request.getAmount().getValue(), request.getValues(), balancesList); + } + + + private void compute(List participants, String requesterId, + Double totalValue, List values, HashMap balancesList) { + + int exactCountIndex = 0; + for (User participant : participants) { + Double newBalance = computeParticipantsBalance(participant.getTotalBalance(), values.get(exactCountIndex++)); + participant.setTotalBalance(newBalance); + balancesList.put(participant.getUserId(), newBalance); + } + User requestor = userService.fetchUserById(requesterId); + Double newBalance = computeRequestorBalance(requestor.getTotalBalance(), totalValue); + requestor.setTotalBalance(newBalance); + balancesList.put(requestor.getUserId(), newBalance); + + } + + private Double computeParticipantsBalance(Double totalbalance, Double equalShare) { + return (totalbalance != null ? totalbalance : 0.0) + -1 * equalShare; + } + + private Double computeRequestorBalance(Double balance, Double equalShare) { + return (balance != null ? balance : 0.0) + equalShare; + } +} diff --git a/src/main/java/handlers/PercentComputeHandler.java b/src/main/java/handlers/PercentComputeHandler.java new file mode 100644 index 0000000..f976bae --- /dev/null +++ b/src/main/java/handlers/PercentComputeHandler.java @@ -0,0 +1,63 @@ +package handlers; + +import impl.UserServiceImpl; +import interfaces.ComputeHandler; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import models.SplitwiseAddRequest; +import models.User; + +import java.util.HashMap; +import java.util.List; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PercentComputeHandler implements ComputeHandler { + private UserServiceImpl userService; + + + @Override + public void compute(SplitwiseAddRequest request, HashMap balancesList) { + List participants = userService.fetchUserByIds(request.getParticipants()); + compute(participants, request.getRequestAdderId(), request.getAmount().getValue(), request.getValues(), request.getParticipants(), balancesList); + + } + + private void compute(List participants, String requesterId, + Double totalValue, List values, List requestParticipants, HashMap balancesList) { + + int percentCount = 0; + //Based on the share each of them will have a value and then it is the almost exact + // For the rest of the users also the same has to be updated + for (User participant : participants) { + Double valueSpent = values.get(percentCount) / 100 * totalValue; + String userId = requestParticipants.get(percentCount); + percentCount++; + + if (userId.equals(requesterId)) { + Double newBalance = computeRequestorBalance(participant.getTotalBalance(), totalValue - valueSpent); + participant.setTotalBalance(newBalance); + balancesList.put(participant.getUserId(), newBalance); + + } else { + Double newBalance = computeParticipantsBalance(participant.getTotalBalance(), valueSpent); + + participant.setTotalBalance(newBalance); + balancesList.put(participant.getUserId(), newBalance); + } + } + } + + private Double computeParticipantsBalance(Double totalbalance, Double equalShare) { + return (totalbalance != null ? totalbalance : 0.0) + -1 * equalShare; + } + + private Double computeRequestorBalance(Double balance, Double equalShare) { + return (balance != null ? balance : 0.0) + equalShare; + } + +} diff --git a/src/main/java/handlers/ShareComputeHandler.java b/src/main/java/handlers/ShareComputeHandler.java new file mode 100644 index 0000000..b145946 --- /dev/null +++ b/src/main/java/handlers/ShareComputeHandler.java @@ -0,0 +1,45 @@ +package handlers; + +import impl.UserServiceImpl; +import interfaces.ComputeHandler; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import models.SplitwiseAddRequest; +import models.User; + +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ShareComputeHandler implements ComputeHandler { + + + private ExactComputeHandler exactComputeHandler; + + // This is more like ratio pay + // 2 1 1 1 -> 1200 + // 2/5*1200 1/5*1200 1/5*1200 1/5*1200 + // This is subset of exact Pay Where Values are computed based on share + + @Override + public void compute(SplitwiseAddRequest request, HashMap balancesList) { + List values = request.getValues(); + // Modify the values and call Exact handler + Double total = values.stream().mapToDouble(Double::doubleValue).sum(); + + values = values.stream() + .map(value -> value * request.getAmount().getValue() / total) + .collect(Collectors.toList()); + request.setValues(values); + exactComputeHandler.compute(request, balancesList); + + } + + +} diff --git a/src/main/java/impl/SplitwiseInterfaceImpl.java b/src/main/java/impl/SplitwiseInterfaceImpl.java new file mode 100644 index 0000000..eedf67b --- /dev/null +++ b/src/main/java/impl/SplitwiseInterfaceImpl.java @@ -0,0 +1,179 @@ +package impl; + +import handlers.EqualComputeHandler; +import handlers.ExactComputeHandler; +import handlers.PercentComputeHandler; +import handlers.ShareComputeHandler; +import interfaces.ComputeHandler; +import interfaces.SplitwiseInterface; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import models.*; +import models.Error; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +public class SplitwiseInterfaceImpl implements SplitwiseInterface { + + private static HashMap balancesList; + + private UserServiceImpl userService; + + @Override + public SplitwiseAddResponse addSplitwiseTransaction(SplitwiseAddRequest request) { + + if (balancesList == null) { + balancesList = new HashMap<>(); + } + + List participants = userService.fetchUserByIds(request.getParticipants()); + if (participants.size() != request.getParticipants().size()) { + return userNotFoundWhileAddingResponse(); + } + + fetchComputeHandlerByExpenseType(request.getExpenseType()).compute(request,balancesList); + return splitAddSuccessResponse(); + } + + private ComputeHandler fetchComputeHandlerByExpenseType(ExpenseType expenseType) { + if (expenseType == ExpenseType.EQUAL) { + return new EqualComputeHandler(userService); + } else if (expenseType == ExpenseType.EXACT) { + return new ExactComputeHandler(userService); + } else if (expenseType == ExpenseType.PERCENT) { + return new PercentComputeHandler(userService); + } else if (expenseType == ExpenseType.SHARE) { + return new ShareComputeHandler(new ExactComputeHandler(userService)); + } + return null; + } + + @Override + public FetchBalanceResponse fetchBalanceByUser(String userId) { + if (balancesList != null && balancesList.containsKey(userId)) { + return FetchBalanceResponse.builder() + .balanceList(null) + .build(); + } + System.out.println("No Balances"); + return noBalanceResponse(); + } + + @Override + public FetchBalanceResponse allBalances() { + if (balancesList != null) { + List balances = reconcileAndReturn(); + if (balances.size() == 0) { + System.out.println("No Balances"); + } + return FetchBalanceResponse.builder() + .balanceList(balances) + .build(); + } + System.out.println("No Balances"); + return noBalanceResponse(); + } + + private FetchBalanceResponse noBalanceResponse() { + List errors = new ArrayList<>(); + errors.add(Error.builder().message("No Balances Found").build()); + return FetchBalanceResponse.builder() + .balanceList(new ArrayList<>()) + .errors(errors) + .build(); + } + + private SplitwiseAddResponse splitAddSuccessResponse() { + + + return SplitwiseAddResponse.builder() + .status(0) + .overAllNetWithEachUser(balancesList) + .balances(reconcileAndReturn()) + .message("SPLITWISE TRANSACTION ADDED SUCCESSFULLY") + .build(); + } + + private SplitwiseAddResponse userNotFoundWhileAddingResponse() { + return SplitwiseAddResponse.builder() + .status(1) + .message("user Not Found While Adding Split") + .build(); + } + + + // Smart Reconciliation + private List reconcileAndReturn() { + List reconciledBalances = new ArrayList<>(); + // We know the net worth of each + // Pick the max Positive + // Pick the max Negative + // Separate positive into one bucket and negative into other + List positive = new ArrayList<>(); + List negative = new ArrayList<>(); + + List allUsers = userService.fetchAllUsers(); + + Double totalPositiveSum = 0D; + for (User allUser : allUsers) { + if (allUser.getTotalBalance() != null && allUser.getTotalBalance() > 0) { + positive.add(allUser); + totalPositiveSum = totalPositiveSum + allUser.getTotalBalance(); + } else if (allUser.getTotalBalance() != null && allUser.getTotalBalance() < 0) { + negative.add(allUser); + } + allUser.setTempBalance(allUser.getTotalBalance()); + } + + int i = 0; + int j = 0; + + + Double remainingBalance = 0.0; + while (totalPositiveSum != 0D) { + // Pick each Negative + User userN = negative.get(i); + User userP = positive.get(j); + + Balance balance = null; + + if (Math.abs(userN.getTempBalance()) >= userP.getTempBalance()) { + j++; + balance = Balance.builder() + .from(userN.getUserId()) + .to(userP.getUserId()) + .balance(userP.getTempBalance() * -1) + .build(); + userN.setTempBalance(userP.getTempBalance() + userN.getTempBalance()); + + } else if (userP.getTempBalance() > Math.abs(userN.getTempBalance())) { + i++; + balance = Balance.builder() + .from(userN.getUserId()) + .to(userP.getUserId()) + .balance(userN.getTotalBalance()) + .build(); + userP.setTempBalance(userP.getTotalBalance() + userN.getTotalBalance()); + } + reconciledBalances.add(balance); + updateBalancesToAUser(balance, userN); + totalPositiveSum += balance.getBalance(); + } + return reconciledBalances; + } + + private void updateBalancesToAUser(Balance balance, User user) { + List balancesList = user.getBalanceList(); + if (balancesList == null) { + balancesList = new ArrayList<>(); + } + balancesList.add(balance); + user.setBalanceList(balancesList); + } + + +} diff --git a/src/main/java/impl/UserServiceImpl.java b/src/main/java/impl/UserServiceImpl.java new file mode 100644 index 0000000..45430b5 --- /dev/null +++ b/src/main/java/impl/UserServiceImpl.java @@ -0,0 +1,61 @@ +package impl; + +import interfaces.IUserInterface; +import models.AddUserResponse; +import models.User; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class UserServiceImpl implements IUserInterface { + + private static final Integer SUCCESS_CODE = 0; + private List userList; + + @Override + public AddUserResponse addUser(User user) { + if (userList == null) { + userList = new ArrayList<>(); + } + // Add any validations if required like email validations before adding + userList.add(user); + return AddUserResponse.builder() + .status(SUCCESS_CODE) + .message("User added successfully") + .build(); + } + + @Override + public List fetchAllUsers() { + return userList; + } + + public User fetchUserById(String userId) { + if (userList != null && userId != null && !userId.isEmpty()) { + return userList.stream() + .filter(user -> user.getUserId().equals(userId)) + .findFirst() + .orElse(null); + } + return null; + } + + public List fetchUserByIds(List userIdList) { + if (userList != null && userIdList != null && !userIdList.isEmpty()) { + return userList.stream() + .filter(user -> userIdList.contains(user.getUserId())) + .collect(Collectors.toList()); + } + return null; + } + + @Override + public void removeUser(String userId) { + + } + + public void printPassBook(User user) { + + } +} diff --git a/src/main/java/interfaces/ComputeHandler.java b/src/main/java/interfaces/ComputeHandler.java new file mode 100644 index 0000000..609952e --- /dev/null +++ b/src/main/java/interfaces/ComputeHandler.java @@ -0,0 +1,10 @@ +package interfaces; + +import models.SplitwiseAddRequest; + +import java.util.HashMap; + +public interface ComputeHandler { + + void compute(SplitwiseAddRequest request, HashMap balancesList); +} diff --git a/src/main/java/interfaces/IUserInterface.java b/src/main/java/interfaces/IUserInterface.java new file mode 100644 index 0000000..5b75149 --- /dev/null +++ b/src/main/java/interfaces/IUserInterface.java @@ -0,0 +1,15 @@ +package interfaces; + +import models.AddUserResponse; +import models.User; + +import java.util.List; + +public interface IUserInterface { + + AddUserResponse addUser(User user); + + List fetchAllUsers(); + + void removeUser(String userId); +} diff --git a/src/main/java/interfaces/SplitwiseInterface.java b/src/main/java/interfaces/SplitwiseInterface.java new file mode 100644 index 0000000..6b5a60a --- /dev/null +++ b/src/main/java/interfaces/SplitwiseInterface.java @@ -0,0 +1,17 @@ +package interfaces; + +import models.FetchBalanceResponse; +import models.SplitwiseAddRequest; +import models.SplitwiseAddResponse; + + +public interface SplitwiseInterface { + + SplitwiseAddResponse addSplitwiseTransaction(SplitwiseAddRequest request); + + FetchBalanceResponse fetchBalanceByUser(String userId); + + FetchBalanceResponse allBalances(); + + +} diff --git a/src/main/java/models/AddUserResponse.java b/src/main/java/models/AddUserResponse.java new file mode 100644 index 0000000..3607d6d --- /dev/null +++ b/src/main/java/models/AddUserResponse.java @@ -0,0 +1,13 @@ +package models; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class AddUserResponse { + + private Integer status; + + private String message; +} diff --git a/src/main/java/models/Amount.java b/src/main/java/models/Amount.java new file mode 100644 index 0000000..943ec30 --- /dev/null +++ b/src/main/java/models/Amount.java @@ -0,0 +1,13 @@ +package models; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Amount { + + private Double value; + private Currency currency = Currency.INR; + +} diff --git a/src/main/java/models/Balance.java b/src/main/java/models/Balance.java new file mode 100644 index 0000000..b58de15 --- /dev/null +++ b/src/main/java/models/Balance.java @@ -0,0 +1,16 @@ +package models; + + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Balance { + + private String from; + + private String to; + + private Double balance; +} diff --git a/src/main/java/models/Currency.java b/src/main/java/models/Currency.java new file mode 100644 index 0000000..2619eb3 --- /dev/null +++ b/src/main/java/models/Currency.java @@ -0,0 +1,6 @@ +package models; + +public enum Currency { + INR, + USD; +} diff --git a/src/main/java/models/Error.java b/src/main/java/models/Error.java new file mode 100644 index 0000000..83facfe --- /dev/null +++ b/src/main/java/models/Error.java @@ -0,0 +1,13 @@ +package models; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Error { + + private String message; + + private String value; +} diff --git a/src/main/java/models/ExpenseType.java b/src/main/java/models/ExpenseType.java new file mode 100644 index 0000000..30ad6de --- /dev/null +++ b/src/main/java/models/ExpenseType.java @@ -0,0 +1,9 @@ +package models; + + +public enum ExpenseType { + EQUAL, + EXACT, + PERCENT, + SHARE; +} diff --git a/src/main/java/models/FetchBalanceResponse.java b/src/main/java/models/FetchBalanceResponse.java new file mode 100644 index 0000000..f8ae89e --- /dev/null +++ b/src/main/java/models/FetchBalanceResponse.java @@ -0,0 +1,16 @@ +package models; + + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class FetchBalanceResponse { + + private List balanceList; + + private List errors; +} diff --git a/src/main/java/models/SplitwiseAddRequest.java b/src/main/java/models/SplitwiseAddRequest.java new file mode 100644 index 0000000..87d65bb --- /dev/null +++ b/src/main/java/models/SplitwiseAddRequest.java @@ -0,0 +1,26 @@ +package models; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class SplitwiseAddRequest { + + private String requestAdderId; + + private Amount amount; + + private String expenseName; + + private List metadata; + + private ExpenseType expenseType; + + private List participants; + + private List values; + +} diff --git a/src/main/java/models/SplitwiseAddResponse.java b/src/main/java/models/SplitwiseAddResponse.java new file mode 100644 index 0000000..eb7483d --- /dev/null +++ b/src/main/java/models/SplitwiseAddResponse.java @@ -0,0 +1,22 @@ +package models; + +import lombok.Builder; +import lombok.Data; + +import java.util.HashMap; +import java.util.List; + +@Data +@Builder +public class SplitwiseAddResponse { + + private HashMap overAllNetWithEachUser; + + private List balances; + + private Integer status; + + private String message; + + private List errorList; +} \ No newline at end of file diff --git a/src/main/java/models/Transaction.java b/src/main/java/models/Transaction.java new file mode 100644 index 0000000..b190e60 --- /dev/null +++ b/src/main/java/models/Transaction.java @@ -0,0 +1,4 @@ +package models; + +public class Transaction { +} diff --git a/src/main/java/models/User.java b/src/main/java/models/User.java new file mode 100644 index 0000000..4831fd9 --- /dev/null +++ b/src/main/java/models/User.java @@ -0,0 +1,27 @@ +package models; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class User { + + private String userId; + + private String name; + + private String email; + + private String phoneNumber; + + private Double totalBalance; + + private List balanceList; + + private Double tempBalance; + + private List transactions; +} diff --git a/src/test/java/SplitwiseApplicationTest.java b/src/test/java/SplitwiseApplicationTest.java new file mode 100644 index 0000000..4928a7f --- /dev/null +++ b/src/test/java/SplitwiseApplicationTest.java @@ -0,0 +1,239 @@ +import com.google.common.collect.Lists; +import impl.SplitwiseInterfaceImpl; +import impl.UserServiceImpl; +import models.*; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +public class SplitwiseApplicationTest { + + + private UserServiceImpl userServiceImpl; + + private SplitwiseInterfaceImpl splitImpl; + + @Test + public void testAddUser() { + userServiceImpl = new UserServiceImpl(); + splitImpl = new SplitwiseInterfaceImpl(userServiceImpl); + + User user1 = User.builder() + .userId("u1") + .name("Nimesh") + .email("nmred@flipkart.com") + .phoneNumber("8888899999") + .build(); + + User user2 = User.builder() + .userId("u2") + .name("Nimesh") + .email("nmreda@flipkart.com") + .phoneNumber("8888899999") + .build(); + + User user3 = User.builder() + .userId("u3") + .name("Nimesh") + .email("nmredb@flipkart.com") + .phoneNumber("8888899999") + .build(); + + User user4 = User.builder() + .userId("u4") + .name("Mekala") + .email("nmredc@flipkart.com") + .phoneNumber("8888899999") + .build(); + + User user5 = User.builder() + .userId("u5") + .name("Reddy") + .email("nmredd@flipkart.com") + .phoneNumber("8888899999") + .build(); + + AddUserResponse response1 = userServiceImpl.addUser(user1); + Assert.assertEquals(response1.getStatus(), Constants.SUCCESS_CODE); + + AddUserResponse response2 = userServiceImpl.addUser(user2); + Assert.assertEquals(response2.getStatus(), Constants.SUCCESS_CODE); + + AddUserResponse response3 = userServiceImpl.addUser(user3); + Assert.assertEquals(response3.getStatus(), Constants.SUCCESS_CODE); + + AddUserResponse response4 = userServiceImpl.addUser(user4); + Assert.assertEquals(response4.getStatus(), Constants.SUCCESS_CODE); + + AddUserResponse response5 = userServiceImpl.addUser(user5); + Assert.assertEquals(response5.getStatus(), Constants.SUCCESS_CODE); + + List userList = userServiceImpl.fetchAllUsers(); + Assert.assertEquals(userList.size(), 5); + + + testAddEqualSplitRequest(); + } + + private void testAddEqualSplitRequest() { + + // SHOW + FetchBalanceResponse response = splitImpl.allBalances(); + Assert.assertEquals(response.getBalanceList().size(), 0); + + + //SHOW U1 + response = splitImpl.fetchBalanceByUser("u1"); + Assert.assertEquals(response.getBalanceList().size(), 0); + + //EXPENSE u1 1000 4 u1 u2 u3 u4 EQUAL + // Add Expense + SplitwiseAddRequest request = SplitwiseAddRequest.builder() + .expenseName("Grocery expense") + .requestAdderId("u1") + .amount(Amount.builder().value(1000.00).build()) + .participants(Lists.newArrayList("u1", "u2", "u3", "u4")) + .expenseType(ExpenseType.EQUAL) + .build(); + + SplitwiseAddResponse splitwiseAddResponse = splitImpl.addSplitwiseTransaction(request); + Assert.assertEquals(splitwiseAddResponse.getStatus(), Constants.SUCCESS_CODE); + Assert.assertNull(splitwiseAddResponse.getErrorList()); + Assert.assertNotNull(splitwiseAddResponse.getOverAllNetWithEachUser()); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u1"), Double.valueOf(750.00)); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u2"), Double.valueOf(-250.00)); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u3"), Double.valueOf(-250.00)); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u4"), Double.valueOf(-250.00)); + Assert.assertEquals(splitwiseAddResponse.getBalances().size(), 3); + print(splitwiseAddResponse.getBalances()); + + fetchBalancesPerUser(); + } + + private void fetchBalancesPerUser() { + User user = userServiceImpl.fetchUserById("u2"); + Assert.assertNotNull(user.getBalanceList()); + Assert.assertNotNull(user.getBalanceList().get(0)); + Assert.assertEquals(user.getBalanceList().size(), 1); + Assert.assertEquals(user.getBalanceList().get(0).getBalance(), Double.valueOf(-250.00)); + Assert.assertEquals(user.getBalanceList().get(0).getFrom(), "u2"); + Assert.assertEquals(user.getBalanceList().get(0).getTo(), "u1"); + + user = userServiceImpl.fetchUserById("u3"); + Assert.assertNotNull(user.getBalanceList()); + Assert.assertNotNull(user.getBalanceList().get(0)); + Assert.assertEquals(user.getBalanceList().size(), 1); + Assert.assertEquals(user.getBalanceList().get(0).getBalance(), Double.valueOf(-250.00)); + Assert.assertEquals(user.getBalanceList().get(0).getFrom(), "u3"); + Assert.assertEquals(user.getBalanceList().get(0).getTo(), "u1"); + + user = userServiceImpl.fetchUserById("u4"); + Assert.assertNotNull(user.getBalanceList()); + Assert.assertNotNull(user.getBalanceList().get(0)); + Assert.assertEquals(user.getBalanceList().size(), 1); + Assert.assertEquals(user.getBalanceList().get(0).getBalance(), Double.valueOf(-250.00)); + Assert.assertEquals(user.getBalanceList().get(0).getFrom(), "u4"); + Assert.assertEquals(user.getBalanceList().get(0).getTo(), "u1"); + print(user.getBalanceList()); + + user = userServiceImpl.fetchUserById("u1"); + Assert.assertNull(user.getBalanceList()); + print(user.getBalanceList()); + + testSplitAddExactRequest(); + } + + private void testSplitAddExactRequest() { + //EXPENSE u1 1250 2 u2 u3 EXACT 370 880 + // Add Expense + SplitwiseAddRequest request = SplitwiseAddRequest.builder() + .requestAdderId("u1") + .amount(Amount.builder().value(1250.00).build()) + .participants(Lists.newArrayList("u2", "u3")) + .values(Lists.newArrayList(370D, 880D)) + .expenseType(ExpenseType.EXACT) + .build(); + SplitwiseAddResponse splitwiseAddResponse = splitImpl.addSplitwiseTransaction(request); + + Assert.assertEquals(splitwiseAddResponse.getStatus(), Constants.SUCCESS_CODE); + Assert.assertNull(splitwiseAddResponse.getErrorList()); + Assert.assertNotNull(splitwiseAddResponse.getOverAllNetWithEachUser()); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u1"), Double.valueOf(2000)); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u2"), Double.valueOf(-620.00)); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u3"), Double.valueOf(-1130.00)); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u4"), Double.valueOf(-250.00)); + Assert.assertEquals(splitwiseAddResponse.getBalances().size(), 3); + print(splitwiseAddResponse.getBalances()); + testSplitPercentageWise(); + } + + private void testSplitPercentageWise() { + //EXPENSE u4 1200 4 u1 u2 u3 u4 PERCENT 40 20 20 20 + // Add Expense + SplitwiseAddRequest request = SplitwiseAddRequest.builder() + .requestAdderId("u4") + .amount(Amount.builder().value(1200.00).build()) + .participants(Lists.newArrayList("u1", "u2", "u3", "u4")) + .values(Lists.newArrayList(40D, 20D, 20D, 20D)) + .expenseType(ExpenseType.PERCENT) + .build(); + + SplitwiseAddResponse splitwiseAddResponse = splitImpl.addSplitwiseTransaction(request); + + Assert.assertEquals(splitwiseAddResponse.getStatus(), Constants.SUCCESS_CODE); + Assert.assertNull(splitwiseAddResponse.getErrorList()); + Assert.assertNotNull(splitwiseAddResponse.getOverAllNetWithEachUser()); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u1"), Double.valueOf(1520)); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u2"), Double.valueOf(-860)); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u3"), Double.valueOf(-1370.00)); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u4"), Double.valueOf(710)); + Assert.assertEquals(splitwiseAddResponse.getBalances().size(), 3); + print(splitwiseAddResponse.getBalances()); + + testShareSplitHandler(); + printPassBook(); + } + + private void testShareSplitHandler() { + // Add Expense + //u4 1200 4 u1 u2 u3 u4 SHARE 2 1 1 1 + + SplitwiseAddRequest request = SplitwiseAddRequest.builder() + .requestAdderId("u4") + .amount(Amount.builder().value(1200.00).build()) + .participants(Lists.newArrayList("u1", "u2", "u3", "u4")) + .values(Lists.newArrayList(2D, 1D, 1D, 1D)) + .expenseType(ExpenseType.SHARE) + .build(); + + SplitwiseAddResponse splitwiseAddResponse = splitImpl.addSplitwiseTransaction(request); + + Assert.assertEquals(splitwiseAddResponse.getStatus(), Constants.SUCCESS_CODE); + Assert.assertNull(splitwiseAddResponse.getErrorList()); + Assert.assertNotNull(splitwiseAddResponse.getOverAllNetWithEachUser()); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u1"), Double.valueOf(1040)); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u2"), Double.valueOf(-1100)); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u3"), Double.valueOf(-1610)); + Assert.assertEquals(splitwiseAddResponse.getOverAllNetWithEachUser().get("u4"), Double.valueOf(1670)); + Assert.assertEquals(splitwiseAddResponse.getBalances().size(), 3); + print(splitwiseAddResponse.getBalances()); + + } + + private void printPassBook() { + List users = userServiceImpl.fetchAllUsers(); + + users.forEach(user -> userServiceImpl.printPassBook(user)); + } + + private void print(List balances) { + System.out.println("========"); + if (balances != null) { + balances.forEach( + balance -> System.out.println("User " + balance.getFrom() + " owes balance " + balance.getBalance() + " to User " + balance.getTo())); + } + System.out.println("========"); + } + +}