@Override boolean isApplicable(final Transaction transaction) { return transaction.getOrientation() == TransactionOrientation.IN && transaction.getCategory() == TransactionCategory.SMP_SELL; }
@Test void correctlyGuessesInvestmentDateFromTransactionHistory() { final Loan loan = Loan.custom() .setId(1) .build(); final Investment i = Investment.fresh(loan, 200) .build(); final RawInvestment r = spy(new RawInvestment(i)); when(r.getInvestmentDate()).thenReturn(null); // enforce so that the date-guessing code has a chance to trigger final PaginatedApi<RawInvestment, PortfolioApi> pa = mockApi(Collections.singletonList(r)); final Transaction irrelevant1 = new Transaction(i, BigDecimal.ZERO, TransactionCategory.SMP_BUY, TransactionOrientation.OUT); final Transaction irrelevant2 = new Transaction(i, BigDecimal.ZERO, TransactionCategory.SMP_SELL, TransactionOrientation.IN); final Transaction relevant = new Transaction(i, BigDecimal.ZERO, TransactionCategory.PAYMENT, TransactionOrientation.IN); final PaginatedApi<Transaction, TransactionApi> ta = mockApi(Arrays.asList(irrelevant1, relevant, irrelevant2)); final Zonky z = mockZonky(pa, ta); final Optional<Investment> result = z.getInvestmentByLoanId(loan.getId()); assertThat(result).isPresent(); final Investment actual = result.get(); final LocalDate investmentDate = actual.getInvestmentDate().toLocalDate(); assertThat(investmentDate).isEqualTo(relevant.getTransactionDate().minusMonths(1)); }
private static Transaction filteredTransfer(final TransactionCategory category) { final Loan l = Loan.custom().setRating(Rating.D).build(); final BigDecimal amount = BigDecimal.valueOf(200); return new Transaction(l, amount, category, TransactionOrientation.IN); }
.orElse(DateUtil.localNow().toLocalDate()).minusDays(i.getDaysPastDue()); final LocalDate lastPayment = getTransactions(i) .filter(t -> t.getCategory() == TransactionCategory.PAYMENT) .map(Transaction::getTransactionDate) .sorted()
@Override void processApplicable(final Transaction transfer) { final int loanId = transfer.getLoanId(); final Investment investment = lookupOrFail(loanId, tenant); final boolean paidInFull = investment.getPaymentStatus() .map(s -> s == PaymentStatus.PAID) .orElse(false); if (!paidInFull) { logger.debug("Not yet repaid in full: {}.", transfer); return; } tenant.fire(loanRepaidLazy(() -> { final Loan loan = tenant.getLoan(loanId); return loanRepaid(investment, loan, tenant.getPortfolio().getOverview()); })); } }
private static long processNewTransactions(final TransactionalPowerTenant tenant, final Stream<Transaction> transactions, final long lastSeenTransactionId) { final Consumer<Transaction> loansRepaid = new LoanRepaidProcessor(tenant); final Consumer<Transaction> participationsSold = new ParticipationSoldProcessor(tenant); return transactions.parallel() // retrieve remote pages in parallel .filter(t -> t.getId() > lastSeenTransactionId) .collect(Collectors.toMap(Transaction::getLoanId, t -> t, DEDUPLICATOR)) // de-duplicate .values() .parallelStream() // possibly thousands of transactions, process them in parallel .peek(loansRepaid) .peek(participationsSold) .mapToLong(Transaction::getId) .max() .orElse(lastSeenTransactionId); }
private static Transaction filteredTransfer(final TransactionCategory category) { final Loan l = Loan.custom().setRating(Rating.D).build(); final BigDecimal amount = BigDecimal.valueOf(200); return new Transaction(l, amount, category, TransactionOrientation.IN); }
.orElse(DateUtil.localNow().toLocalDate()).minusDays(i.getDaysPastDue()); final LocalDate lastPayment = getTransactions(i) .filter(t -> t.getCategory() == TransactionCategory.PAYMENT) .map(Transaction::getTransactionDate) .sorted()
@Override void processApplicable(final Transaction transfer) { final int loanId = transfer.getLoanId(); final Investment investment = lookupOrFail(loanId, tenant); final boolean paidInFull = investment.getPaymentStatus() .map(s -> s == PaymentStatus.PAID) .orElse(false); if (!paidInFull) { logger.debug("Not yet repaid in full: {}.", transfer); return; } tenant.fire(loanRepaidLazy(() -> { final Loan loan = LoanCache.get().getLoan(loanId, tenant); return loanRepaid(investment, loan, tenant.getPortfolio().getOverview()); })); } }
private static long processNewTransactions(final TransactionalPowerTenant tenant, final Stream<Transaction> transactions, final long lastSeenTransactionId) { final Consumer<Transaction> loansRepaid = new LoanRepaidProcessor(tenant); final Consumer<Transaction> participationsSold = new ParticipationSoldProcessor(tenant); return transactions.parallel() // retrieve remote pages in parallel .filter(t -> t.getId() > lastSeenTransactionId) .collect(Collectors.toMap(Transaction::getLoanId, t -> t, DEDUPLICATOR)) // de-duplicate .values() .parallelStream() // possibly thousands of transactions, process them in parallel .peek(loansRepaid) .peek(participationsSold) .mapToLong(Transaction::getId) .max() .orElse(lastSeenTransactionId); }
@Override boolean isApplicable(final Transaction transaction) { return transaction.getOrientation() == TransactionOrientation.IN && transaction.getCategory() == TransactionCategory.SMP_SELL; }
@Test public void queriesAndUpdatesWhenNewTransactionsFound() { state.update(m -> m.put(IncomeProcessor.STATE_KEY, "1")); final Loan l1 = Loan.custom().build(); final Transaction t1 = new Transaction(1, l1, BigDecimal.TEN, TransactionCategory.PAYMENT, TransactionOrientation.IN); final Loan l2 = Loan.custom().build(); final Transaction t2 = new Transaction(2, l2, BigDecimal.ONE, TransactionCategory.SMP_SELL, TransactionOrientation.IN); final Investment i2 = Investment.fresh(l2, BigDecimal.ONE).build(); final Loan l3 = Loan.custom().build(); final Investment i3 = Investment.fresh(l3, BigDecimal.TEN) .setPaymentStatus(PaymentStatus.PAID) .build(); final Transaction t3 = new Transaction(3, l3, BigDecimal.TEN, TransactionCategory.PAYMENT, TransactionOrientation.IN); when(zonky.getTransactions((Select) any())).thenAnswer(i -> Stream.of(t2, t3, t1)); when(zonky.getLoan(eq(l2.getId()))).thenReturn(l2); when(zonky.getLoan(eq(l3.getId()))).thenReturn(l3); when(zonky.getInvestmentByLoanId(eq(l2.getId()))).thenReturn(Optional.of(i2)); when(zonky.getInvestmentByLoanId(eq(l3.getId()))).thenReturn(Optional.of(i3)); processor.accept(tenant); verify(zonky, times(1)).getTransactions((Select) any()); assertThat(state.getValue(IncomeProcessor.STATE_KEY)).hasValue("3"); // new maximum assertThat(getEventsRequested()).hasSize(2); } }
@Override void processApplicable(final Transaction transaction) { final int loanId = transaction.getLoanId(); SoldParticipationCache.forTenant(tenant).markAsSold(loanId); tenant.fire(investmentSoldLazy(() -> { final Investment i = lookupOrFail(loanId, tenant); final Loan l = tenant.getLoan(loanId); return investmentSold(i, l, tenant.getPortfolio().getOverview()); })); } }
@Override boolean isApplicable(final Transaction transaction) { return transaction.getCategory() == TransactionCategory.PAYMENT && transaction.getOrientation() == TransactionOrientation.IN; }
@Override void processApplicable(final Transaction transaction) { final int loanId = transaction.getLoanId(); SoldParticipationCache.forTenant(tenant).markAsSold(loanId); tenant.fire(investmentSoldLazy(() -> { final Investment i = lookupOrFail(loanId, tenant); final Loan l = LoanCache.get().getLoan(loanId, tenant); return investmentSold(i, l, tenant.getPortfolio().getOverview()); })); } }
@Override boolean isApplicable(final Transaction transaction) { return transaction.getCategory() == TransactionCategory.PAYMENT && transaction.getOrientation() == TransactionOrientation.IN; }