Skip to content

Commit 5140fec

Browse files
author
Mateusz Czeladka
committed
feat: ability to reject multiple transactions.
1 parent 0d942c7 commit 5140fec

File tree

9 files changed

+315
-234
lines changed

9 files changed

+315
-234
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.cardanofoundation.lob.app.accounting_reporting_core.domain.core;
2+
3+
import lombok.Getter;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.ToString;
6+
import org.zalando.problem.Problem;
7+
8+
@RequiredArgsConstructor
9+
@ToString
10+
@Getter
11+
public class TransactionProblem {
12+
13+
private final String id;
14+
private final Problem problem;
15+
16+
}

accounting_reporting_core/src/main/java/org/cardanofoundation/lob/app/accounting_reporting_core/repository/TransactionBatchRepositoryGateway.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@ public Optional<TransactionBatchEntity> findById(String batchId) {
2222
return transactionBatchRepository.findById(batchId);
2323
}
2424

25+
// TODO: Pagination need to be implemented
2526
public List<TransactionBatchEntity> findByOrganisationId(String organisationId) {
26-
/**
27-
* Todo: Pagination need to be implemented.
28-
*/
2927
return transactionBatchRepository.findAllByFilteringParametersOrganisationId(organisationId);
3028
}
3129

@@ -36,4 +34,5 @@ public List<TransactionBatchEntity> findByFilter(BatchSearchRequest body) {
3634
public Long findByFilterCount(BatchSearchRequest body) {
3735
return transactionBatchRepository.findByFilterCount(body);
3836
}
37+
3938
}

accounting_reporting_core/src/main/java/org/cardanofoundation/lob/app/accounting_reporting_core/repository/TransactionRepositoryGateway.java

Lines changed: 178 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,27 @@
44
import lombok.RequiredArgsConstructor;
55
import lombok.extern.slf4j.Slf4j;
66
import lombok.val;
7+
import org.cardanofoundation.lob.app.accounting_reporting_core.domain.core.TransactionProblem;
78
import org.cardanofoundation.lob.app.accounting_reporting_core.domain.core.TransactionType;
89
import org.cardanofoundation.lob.app.accounting_reporting_core.domain.core.ValidationStatus;
910
import org.cardanofoundation.lob.app.accounting_reporting_core.domain.entity.Rejection;
1011
import org.cardanofoundation.lob.app.accounting_reporting_core.domain.entity.TransactionEntity;
12+
import org.cardanofoundation.lob.app.accounting_reporting_core.resource.requests.TransactionId;
13+
import org.cardanofoundation.lob.app.accounting_reporting_core.resource.requests.TransactionsRequest;
1114
import org.cardanofoundation.lob.app.accounting_reporting_core.service.internal.LedgerService;
15+
import org.springframework.dao.DataAccessException;
1216
import org.springframework.stereotype.Service;
1317
import org.springframework.transaction.annotation.Transactional;
1418
import org.zalando.problem.Problem;
1519

20+
import java.util.ArrayList;
1621
import java.util.List;
1722
import java.util.Optional;
1823
import java.util.Set;
1924
import java.util.stream.Collectors;
2025

21-
import static org.cardanofoundation.lob.app.accounting_reporting_core.domain.core.ValidationStatus.FAILED;
26+
import static org.cardanofoundation.lob.app.accounting_reporting_core.domain.core.TransactionStatus.FAIL;
27+
import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW;
2228

2329
@Service
2430
@Slf4j
@@ -30,119 +36,220 @@ public class TransactionRepositoryGateway {
3036
private final TransactionRepository transactionRepository;
3137
private final LedgerService ledgerService;
3238

33-
@Transactional
34-
public Either<Problem, Boolean> approveTransaction(String transactionId) {
39+
@Transactional(propagation = REQUIRES_NEW)
40+
// TODO optimise performance because we have to load transaction from db each time and we don't save it in bulk
41+
private Either<TransactionProblem, TransactionEntity> approveTransaction(String transactionId) {
3542
log.info("Approving transaction: {}", transactionId);
3643

3744
val txM = transactionRepository.findById(transactionId);
3845

3946
if (txM.isEmpty()) {
40-
return Either.left(Problem.builder()
47+
val problem = Problem.builder()
4148
.withTitle("TX_NOT_FOUND")
4249
.withDetail(STR."Transaction with id \{transactionId} not found")
4350
.with("transactionId", transactionId)
44-
.build()
45-
);
51+
.build();
52+
53+
return Either.left(new TransactionProblem(transactionId, problem));
4654
}
4755

4856
val tx = txM.orElseThrow();
4957

50-
if (tx.getAutomatedValidationStatus() == FAILED) {
51-
return Either.left(Problem.builder()
58+
if (tx.getStatus() == FAIL) {
59+
val problem = Problem.builder()
5260
.withTitle("CANNOT_APPROVE_FAILED_TX")
5361
.withDetail(STR."Cannot approve a failed transaction, transactionId: \{transactionId}")
5462
.with("transactionId", transactionId)
55-
.build()
56-
);
63+
.build();
64+
65+
return Either.left(new TransactionProblem(transactionId, problem));
5766
}
5867

5968
tx.setTransactionApproved(true);
6069

6170
val savedTx = transactionRepository.save(tx);
62-
val organisationId = savedTx.getOrganisation().getId();
6371

64-
if (savedTx.getTransactionApproved()) {
65-
ledgerService.checkIfThereAreTransactionsToDispatch(organisationId, Set.of(savedTx));
72+
return Either.right(savedTx);
73+
}
74+
75+
@Transactional(propagation = REQUIRES_NEW)
76+
// TODO optimise performance because we have to load transaction from db each time and we don't save it in bulk
77+
private Either<TransactionProblem, TransactionEntity> approveTransactionsDispatch(String transactionId) {
78+
log.info("Approving transaction to dispatch: {}", transactionId);
79+
80+
val txM = transactionRepository.findById(transactionId);
81+
82+
if (txM.isEmpty()) {
83+
val problem = Problem.builder()
84+
.withTitle("TX_NOT_FOUND")
85+
.withDetail(STR."Transaction with id \{transactionId} not found")
86+
.with("transactionId", transactionId)
87+
.build();
6688

67-
return Either.right(savedTx.getTransactionApproved());
89+
return Either.left(new TransactionProblem(transactionId, problem));
6890
}
6991

70-
return Either.right(false);
71-
}
92+
val tx = txM.orElseThrow();
7293

73-
@Transactional
74-
public Set<String> approveTransactions(String organisationId, Set<String> transactionIds) {
75-
log.info("Approving transactions: {}", transactionIds);
94+
if (tx.getStatus() == FAIL) {
95+
val problem = Problem.builder()
96+
.withTitle("CANNOT_APPROVE_FAILED_TX")
97+
.withDetail(STR."Cannot approve a failed transaction, transactionId: \{transactionId}")
98+
.with("transactionId", transactionId)
99+
.build();
76100

77-
val transactions = transactionRepository.findAllById(transactionIds)
78-
.stream()
79-
.filter(tx -> tx.getAutomatedValidationStatus() != FAILED)
80-
.peek(tx -> tx.setTransactionApproved(true))
81-
.collect(Collectors.toSet());
101+
return Either.left(new TransactionProblem(transactionId, problem));
102+
}
82103

83-
val savedTxs = transactionRepository.saveAll(transactions);
104+
if (!tx.getTransactionApproved()) {
105+
val problem = Problem.builder()
106+
.withTitle("TX_NOT_APPROVED")
107+
.withDetail(STR."Cannot approve a transaction that has not been approved before, transactionId: \{transactionId}")
108+
.with("transactionId", transactionId)
109+
.build();
110+
111+
return Either.left(new TransactionProblem(transactionId, problem));
112+
}
113+
114+
tx.setLedgerDispatchApproved(true);
84115

85-
ledgerService.checkIfThereAreTransactionsToDispatch(organisationId, Set.copyOf(savedTxs));
116+
val savedTx = transactionRepository.save(tx);
86117

87-
return savedTxs.stream().map(TransactionEntity::getId).collect(Collectors.toSet());
118+
return Either.right(savedTx);
88119
}
89120

90-
@Transactional
91-
public Set<String> approveTransactionsDispatch(String organisationId, Set<String> transactionIds) {
92-
log.info("Approving transactions dispatch: {}", transactionIds);
121+
public List<Either<TransactionProblem, TransactionEntity>> approveTransactions(TransactionsRequest transactionsRequest) {
122+
val organisationId = transactionsRequest.getOrganisationId();
93123

94-
val transactions = transactionRepository.findAllById(transactionIds)
124+
val transactionIds = transactionsRequest.getTransactionIds()
95125
.stream()
96-
.filter(tx -> tx.getAutomatedValidationStatus() != FAILED)
97-
.peek(tx -> tx.setLedgerDispatchApproved(true))
126+
.map(TransactionId::getId)
98127
.collect(Collectors.toSet());
99128

100-
val savedTxs = transactionRepository.saveAll(transactions);
129+
val transactionsApprovalResponseListE = new ArrayList<Either<TransactionProblem, TransactionEntity>>();
130+
for (val transactionId : transactionIds) {
131+
try {
132+
val transactionEntities = approveTransaction(transactionId);
101133

102-
ledgerService.checkIfThereAreTransactionsToDispatch(organisationId, Set.copyOf(savedTxs));
134+
transactionsApprovalResponseListE.add(transactionEntities);
135+
} catch (DataAccessException dae) {
136+
log.error("Error approving transaction: {}", transactionId, dae);
103137

104-
return savedTxs.stream().map(TransactionEntity::getId).collect(Collectors.toSet());
105-
}
138+
val problem = Problem.builder()
139+
.withTitle("DB_ERROR")
140+
.withDetail(STR."DB error approving transaction: \{transactionId}")
141+
.with("transactionId", transactionId)
142+
.with("error", dae.getMessage())
143+
.build();
106144

107-
@Transactional
108-
public Either<Problem, Boolean> approveTransactionDispatch(String transactionId) {
109-
log.info("Approving transaction dispatch: {}", transactionId);
145+
transactionsApprovalResponseListE.add(Either.left(new TransactionProblem(transactionId, problem)));
146+
}
147+
}
110148

111-
val txM = transactionRepository.findById(transactionId);
149+
val transactionSuccesses = transactionsApprovalResponseListE.stream()
150+
.filter(Either::isRight)
151+
.map(Either::get)
152+
.collect(Collectors.toSet());
112153

113-
if (txM.isEmpty()) {
114-
return Either.left(Problem.builder()
115-
.withTitle("TX_NOT_FOUND")
116-
.withDetail(STR."Transaction with id \{transactionId} not found")
117-
.with("transactionId", transactionId)
118-
.build()
119-
);
120-
}
154+
ledgerService.checkIfThereAreTransactionsToDispatch(organisationId, transactionSuccesses);
121155

122-
val tx = txM.orElseThrow();
156+
return transactionsApprovalResponseListE;
157+
}
123158

124-
if (tx.getAutomatedValidationStatus() == FAILED) {
125-
return Either.left(Problem.builder()
126-
.withTitle("CANNOT_APPROVE_FAILED_TX")
127-
.withDetail(STR."Cannot approve dispatch for a failed transaction, transactionId: \{transactionId}")
128-
.with("transactionId", transactionId)
129-
.build()
130-
);
131-
}
159+
public List<Either<TransactionProblem, TransactionEntity>> approveTransactionsDispatch(TransactionsRequest transactionsRequest) {
160+
val organisationId = transactionsRequest.getOrganisationId();
132161

133-
tx.setLedgerDispatchApproved(true);
162+
val transactionIds = transactionsRequest.getTransactionIds()
163+
.stream()
164+
.map(TransactionId::getId)
165+
.collect(Collectors.toSet());
134166

135-
val savedTx = transactionRepository.save(tx);
167+
val transactionsApprovalResponseListE = new ArrayList<Either<TransactionProblem, TransactionEntity>>();
168+
for (val transactionId : transactionIds) {
169+
try {
170+
val transactionEntities = approveTransactionsDispatch(transactionId);
136171

137-
if (savedTx.getLedgerDispatchApproved()) {
138-
ledgerService.checkIfThereAreTransactionsToDispatch(savedTx.getOrganisation().getId(), Set.of(savedTx));
172+
transactionsApprovalResponseListE.add(transactionEntities);
173+
} catch (DataAccessException dae) {
174+
log.error("Error approving transaction publish: {}", transactionId, dae);
139175

140-
return Either.right(savedTx.getLedgerDispatchApproved());
176+
val problem = Problem.builder()
177+
.withTitle("DB_ERROR")
178+
.withDetail(STR."DB error approving transaction publish:\{transactionId}")
179+
.with("transactionId", transactionId)
180+
.with("error", dae.getMessage())
181+
.build();
182+
183+
transactionsApprovalResponseListE.add(Either.left(new TransactionProblem(transactionId, problem)));
184+
}
141185
}
142186

143-
return Either.right(false);
187+
val transactionSuccesses = transactionsApprovalResponseListE.stream()
188+
.filter(Either::isRight)
189+
.map(Either::get)
190+
.collect(Collectors.toSet());
191+
192+
ledgerService.checkIfThereAreTransactionsToDispatch(organisationId, transactionSuccesses);
193+
194+
return transactionsApprovalResponseListE;
144195
}
145196

197+
// @Transactional
198+
// public Set<String> approveTransactionsDispatch(String organisationId, Set<String> transactionIds) {
199+
// log.info("Approving transactions dispatch: {}", transactionIds);
200+
//
201+
// val transactions = transactionRepository.findAllById(transactionIds)
202+
// .stream()
203+
// .filter(tx -> tx.getAutomatedValidationStatus() != FAILED)
204+
// .peek(tx -> tx.setLedgerDispatchApproved(true))
205+
// .collect(Collectors.toSet());
206+
//
207+
// val savedTxs = transactionRepository.saveAll(transactions);
208+
//
209+
// ledgerService.checkIfThereAreTransactionsToDispatch(organisationId, Set.copyOf(savedTxs));
210+
//
211+
// return savedTxs.stream().map(TransactionEntity::getId).collect(Collectors.toSet());
212+
// }
213+
214+
// @Transactional
215+
// public Either<Problem, Boolean> approveTransactionDispatch(String transactionId) {
216+
// log.info("Approving transaction dispatch: {}", transactionId);
217+
//
218+
// val txM = transactionRepository.findById(transactionId);
219+
//
220+
// if (txM.isEmpty()) {
221+
// return Either.left(Problem.builder()
222+
// .withTitle("TX_NOT_FOUND")
223+
// .withDetail(STR."Transaction with id \{transactionId} not found")
224+
// .with("transactionId", transactionId)
225+
// .build()
226+
// );
227+
// }
228+
//
229+
// val tx = txM.orElseThrow();
230+
//
231+
// if (tx.getAutomatedValidationStatus() == FAILED) {
232+
// return Either.left(Problem.builder()
233+
// .withTitle("CANNOT_APPROVE_FAILED_TX")
234+
// .withDetail(STR."Cannot approve dispatch for a failed transaction, transactionId: \{transactionId}")
235+
// .with("transactionId", transactionId)
236+
// .build()
237+
// );
238+
// }
239+
//
240+
// tx.setLedgerDispatchApproved(true);
241+
//
242+
// val savedTx = transactionRepository.save(tx);
243+
//
244+
// if (savedTx.getLedgerDispatchApproved()) {
245+
// ledgerService.checkIfThereAreTransactionsToDispatch(savedTx.getOrganisation().getId(), Set.of(savedTx));
246+
//
247+
// return Either.right(savedTx.getLedgerDispatchApproved());
248+
// }
249+
//
250+
// return Either.right(false);
251+
// }
252+
146253
public Either<Problem, Boolean> changeTransactionComment(String txId, String userComment) {
147254
val txM = transactionRepository.findById(txId);
148255

@@ -205,16 +312,15 @@ public Optional<TransactionEntity> findById(String transactionId) {
205312
}
206313

207314
public List<TransactionEntity> findByAllId(Set<String> transactionIds) {
208-
209315
return transactionRepository.findAllById(transactionIds);
210316
}
211317

212-
private static Set<String> transactionIds(Set<TransactionEntity> transactions) {
213-
return transactions
214-
.stream()
215-
.map(TransactionEntity::getId)
216-
.collect(Collectors.toSet());
217-
}
318+
// private static Set<String> transactionIds(Set<TransactionEntity> transactions) {
319+
// return transactions
320+
// .stream()
321+
// .map(TransactionEntity::getId)
322+
// .collect(Collectors.toSet());
323+
// }
218324

219325
public List<TransactionEntity> findAllByStatus(String organisationId,
220326
List<ValidationStatus> validationStatuses,
@@ -225,4 +331,5 @@ public List<TransactionEntity> findAllByStatus(String organisationId,
225331
public List<TransactionEntity> listAll() {
226332
return transactionRepository.findAll();
227333
}
334+
228335
}

0 commit comments

Comments
 (0)