Skip to content

Commit 11b5149

Browse files
authored
release/1.0.1 (#344)
* feat: adding pagination to public pages endpoint (#342) * feat: adding pagination to public pages endpoint * chore: renaming size to limit * chore: adding tests * chore: spotless * fix: adding proper serialization for report numbers (#345) * fix: adding proper serialization for report numbers * fix: normalising decimal strings properly * fix: moving page and limit to request params (#346) * fix: moving page and limit to request params * chore: adjusted tests * fix: add pagination after aggregation (#347) * fix: add pagination after aggregation * Fix/pagination after aggregation extension (#348) * fix: add pagination after aggregation * chore: doing pagination in the database - there is still one optimization possible. By doing everything with one query instead of two * chore: removing unused code
1 parent 1b05eda commit 11b5149

File tree

11 files changed

+229
-82
lines changed

11 files changed

+229
-82
lines changed

accounting_reporting_core/src/main/java/org/cardanofoundation/lob/app/accounting_reporting_core/domain/entity/TransactionEntity.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
@AllArgsConstructor
4242
@Audited
4343
@EntityListeners({ OverallStatusTransactionEntityListener.class, AuditingEntityListener.class })
44+
@Builder
4445
public class TransactionEntity extends CommonEntity implements Persistable<String> {
4546

4647
@Id

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

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.math.BigDecimal;
44
import java.time.LocalDate;
55
import java.util.List;
6+
import java.util.Map;
67
import java.util.Optional;
78
import java.util.Set;
89
import java.util.stream.Collectors;
@@ -19,13 +20,15 @@
1920
import org.cardanofoundation.lob.app.accounting_reporting_core.domain.core.LedgerDispatchStatus;
2021
import org.cardanofoundation.lob.app.accounting_reporting_core.domain.core.TxItemValidationStatus;
2122
import org.cardanofoundation.lob.app.accounting_reporting_core.domain.entity.TransactionItemEntity;
23+
import org.cardanofoundation.lob.app.accounting_reporting_core.resource.views.TransactionItemAggregateView;
2224

2325
@Slf4j
2426
@RequiredArgsConstructor
2527
@Service
2628
public class TransactionItemExtractionRepository {
2729

2830
private final EntityManager em;
31+
private final TransactionItemRepository transactionItemRepository;
2932

3033
public List<TransactionItemEntity> findByItemAccount(LocalDate dateFrom, LocalDate dateTo,
3134
List<String> accountCode, List<String> costCenter,
@@ -118,15 +121,60 @@ WHERE oc.subType.id IN (
118121
return query.getResultList();
119122
}
120123

124+
public long countItemsByAccountDateAggregated(String orgId, LocalDate dateFrom, LocalDate dateTo, Set<String> event, Set<String> currency, Optional<BigDecimal> minAmount, Optional<BigDecimal> maxAmount, Set<String> transactionHash) {
125+
String jpql = """
126+
SELECT COUNT(1) FROM accounting_reporting_core.TransactionItemEntity ti
127+
JOIN ti.transaction te LEFT JOIN OrganisationCostCenter cc ON ti.costCenter.customerCode = cc.id.customerCode
128+
""";
121129

122-
public List<TransactionItemEntity> findByItemAccountDate(String orgId, LocalDate dateFrom, LocalDate dateTo, Set<String> event, Set<String> currency, Optional<BigDecimal> minAmount, Optional<BigDecimal> maxAmount, Set<String> transactionHash) {
130+
String where = constructWhereClauseForExtraction(orgId, event, currency, minAmount, maxAmount, transactionHash);
131+
String grouping = "GROUP BY ti.accountEvent.code, ti.fxRate, ti.project.customerCode, cc.parentCustomerCode, ti.document.num";
132+
Query resultQuery = em.createQuery(jpql + where + grouping);
123133

124-
minAmount = Optional.ofNullable(minAmount).orElse(Optional.empty());
125-
maxAmount = Optional.ofNullable(maxAmount).orElse(Optional.empty());
134+
resultQuery.setParameter("dateFrom", dateFrom);
135+
resultQuery.setParameter("dateTo", dateTo);
136+
137+
return resultQuery.getResultList().size();
138+
}
139+
140+
public List<TransactionItemEntity> findByItemAccountDateAggregated(String orgId, LocalDate dateFrom, LocalDate dateTo, Set<String> event, Set<String> currency, Optional<BigDecimal> minAmount, Optional<BigDecimal> maxAmount, Set<String> transactionHash, int page, int limit) {
126141

127142
String jpql = """
128-
SELECT ti FROM accounting_reporting_core.TransactionItemEntity ti INNER JOIN ti.transaction te
143+
SELECT NEW org.cardanofoundation.lob.app.accounting_reporting_core.resource.views.TransactionItemAggregateView(MIN(ti.id), SUM(ti.amountLcy), SUM(ti.amountFcy))
144+
FROM accounting_reporting_core.TransactionItemEntity ti
145+
JOIN ti.transaction te LEFT JOIN OrganisationCostCenter cc ON ti.costCenter.customerCode = cc.id.customerCode
129146
""";
147+
148+
String where = constructWhereClauseForExtraction(orgId, event, currency, minAmount, maxAmount, transactionHash);
149+
150+
String grouping = "GROUP BY ti.accountEvent.code, ti.fxRate, ti.project.customerCode, cc.parentCustomerCode, ti.document.num";
151+
Query resultQuery = em.createQuery(jpql + where + grouping);
152+
153+
resultQuery.setParameter("dateFrom", dateFrom);
154+
resultQuery.setParameter("dateTo", dateTo);
155+
156+
// adding pagination
157+
resultQuery.setFirstResult(page * limit);
158+
resultQuery.setMaxResults(limit);
159+
List<TransactionItemAggregateView> itemAggregateViews = resultQuery.getResultList();
160+
Map<String, TransactionItemAggregateView> collect =
161+
itemAggregateViews.stream().collect(Collectors.toMap(TransactionItemAggregateView::getTxId, item -> item));
162+
List<TransactionItemEntity> allById = transactionItemRepository.findAllById(collect.keySet());
163+
allById.stream().forEach(item -> {
164+
TransactionItemAggregateView aggregateView = collect.get(item.getId());
165+
if (aggregateView != null) {
166+
item.setAmountFcy(aggregateView.getAmountFcyAggregated());
167+
item.setAmountLcy(aggregateView.getAmountLcyAggregated());
168+
}
169+
});
170+
return allById;
171+
}
172+
173+
private static String constructWhereClauseForExtraction(String orgId, Set<String> event, Set<String> currency, Optional<BigDecimal> minAmount, Optional<BigDecimal> maxAmount, Set<String> transactionHash) {
174+
minAmount = Optional.ofNullable(minAmount).orElse(Optional.empty());
175+
maxAmount = Optional.ofNullable(maxAmount).orElse(Optional.empty());
176+
177+
130178
String where = STR."""
131179
WHERE te.entryDate >= :dateFrom AND te.entryDate <= :dateTo
132180
AND te.organisation.id = '\{orgId}'
@@ -166,13 +214,7 @@ AND ABS(ti.amountFcy) <= \{maxAmount.get()}
166214
where += STR."""
167215
AND te.ledgerDispatchStatus = '\{LedgerDispatchStatus.FINALIZED}'
168216
""";
169-
170-
Query resultQuery = em.createQuery(jpql + where);
171-
172-
resultQuery.setParameter("dateFrom", dateFrom);
173-
resultQuery.setParameter("dateTo", dateTo);
174-
175-
return resultQuery.getResultList();
217+
return where;
176218
}
177219

178220

accounting_reporting_core/src/main/java/org/cardanofoundation/lob/app/accounting_reporting_core/resource/PublicInterfaceController.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@
33
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
44
import static org.zalando.problem.Status.NOT_FOUND;
55

6+
import java.util.Optional;
67
import java.util.stream.Collectors;
78

89
import jakarta.validation.Valid;
910

1011
import lombok.RequiredArgsConstructor;
1112
import lombok.extern.slf4j.Slf4j;
12-
import lombok.val;
1313

1414
import org.springframework.http.ResponseEntity;
1515
import org.springframework.web.bind.annotation.PostMapping;
1616
import org.springframework.web.bind.annotation.RequestBody;
1717
import org.springframework.web.bind.annotation.RequestMapping;
18+
import org.springframework.web.bind.annotation.RequestParam;
1819
import org.springframework.web.bind.annotation.RestController;
1920

2021
import io.swagger.v3.oas.annotations.Operation;
@@ -24,6 +25,7 @@
2425
import io.swagger.v3.oas.annotations.responses.ApiResponse;
2526
import io.swagger.v3.oas.annotations.tags.Tag;
2627
import org.zalando.problem.Problem;
28+
import org.zalando.problem.ThrowableProblem;
2729

2830
import org.cardanofoundation.lob.app.accounting_reporting_core.resource.presentation_layer_service.ExtractionItemService;
2931
import org.cardanofoundation.lob.app.accounting_reporting_core.resource.presentation_layer_service.ReportViewService;
@@ -33,6 +35,7 @@
3335
import org.cardanofoundation.lob.app.accounting_reporting_core.resource.views.ReportResponseView;
3436
import org.cardanofoundation.lob.app.accounting_reporting_core.service.internal.ReportService;
3537
import org.cardanofoundation.lob.app.organisation.OrganisationPublicApi;
38+
import org.cardanofoundation.lob.app.organisation.domain.entity.Organisation;
3639

3740
@RestController
3841
@RequestMapping("/api/v1/public")
@@ -56,10 +59,10 @@ public class PublicInterfaceController {
5659
@PostMapping(value = "/reports", produces = "application/json")
5760
public ResponseEntity<ReportResponseView> reportSearchPublicInterface(@Valid @RequestBody PublicReportSearchRequest reportSearchRequest) {
5861

59-
val orgM = organisationPublicApi.findByOrganisationId(reportSearchRequest.getOrganisationId());
62+
Optional<Organisation> orgM = organisationPublicApi.findByOrganisationId(reportSearchRequest.getOrganisationId());
6063

6164
if (orgM.isEmpty()) {
62-
val issue = Problem.builder()
65+
ThrowableProblem issue = Problem.builder()
6366
.withTitle("ORGANISATION_NOT_FOUND")
6467
.withDetail(STR."Unable to find Organisation by Id: \{reportSearchRequest.getOrganisationId()}")
6568
.withStatus(NOT_FOUND)
@@ -87,11 +90,13 @@ public ResponseEntity<ReportResponseView> reportSearchPublicInterface(@Valid @Re
8790
})
8891
}
8992
)
90-
public ResponseEntity<ExtractionTransactionView> transactionSearchPublicInterface(@Valid @RequestBody PublicInterfaceTransactionsRequest transactionsRequest) {
91-
val orgM = organisationPublicApi.findByOrganisationId(transactionsRequest.getOrganisationId());
93+
public ResponseEntity<ExtractionTransactionView> transactionSearchPublicInterface(@Valid @RequestBody PublicInterfaceTransactionsRequest transactionsRequest,
94+
@RequestParam(name = "page", defaultValue = "0") int page,
95+
@RequestParam(name = "limit", defaultValue = "100") int limit) {
96+
Optional<Organisation> orgM = organisationPublicApi.findByOrganisationId(transactionsRequest.getOrganisationId());
9297

9398
if (orgM.isEmpty()) {
94-
val issue = Problem.builder()
99+
ThrowableProblem issue = Problem.builder()
95100
.withTitle("ORGANISATION_NOT_FOUND")
96101
.withDetail(STR."Unable to find Organisation by Id: \{transactionsRequest.getOrganisationId()}")
97102
.withStatus(NOT_FOUND)
@@ -102,16 +107,18 @@ public ResponseEntity<ExtractionTransactionView> transactionSearchPublicInterfac
102107

103108
return ResponseEntity
104109
.ok()
105-
.body(ExtractionTransactionView.createSuccess(extractionItemService.findTransactionItemsPublic(
110+
.body(extractionItemService.findTransactionItemsPublic(
106111
transactionsRequest.getOrganisationId(),
107112
transactionsRequest.getDateFrom(),
108113
transactionsRequest.getDateTo(),
109114
transactionsRequest.getEvents(),
110115
transactionsRequest.getCurrency(),
111116
transactionsRequest.getMinAmount(),
112117
transactionsRequest.getMaxAmount(),
113-
transactionsRequest.getTransactionHashes()
114-
))
118+
transactionsRequest.getTransactionHashes(),
119+
page,
120+
limit
121+
)
115122
);
116123
}
117124
}

accounting_reporting_core/src/main/java/org/cardanofoundation/lob/app/accounting_reporting_core/resource/presentation_layer_service/ExtractionItemService.java

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import java.util.List;
88
import java.util.Optional;
99
import java.util.Set;
10-
import java.util.stream.Collectors;
1110

1211
import lombok.RequiredArgsConstructor;
1312
import lombok.extern.slf4j.Slf4j;
@@ -40,29 +39,15 @@ public ExtractionTransactionView findTransactionItems(LocalDate dateFrom, LocalD
4039
List<ExtractionTransactionItemView> transactionItem = transactionItemRepositoryImpl.findByItemAccount(dateFrom, dateTo, accountCode, costCenter, project, accountType, accountSubType)
4140
.stream().map(this::extractionTransactionItemViewBuilder).toList();
4241

43-
return ExtractionTransactionView.createSuccess(transactionItem);
42+
return ExtractionTransactionView.createSuccess(transactionItem, transactionItem.size(), 0, transactionItem.size());
4443
}
4544

4645
@Transactional(readOnly = true)
47-
public List<ExtractionTransactionItemView> findTransactionItemsPublic(String orgId, LocalDate dateFrom, LocalDate dateTo, Set<String> event, Set<String> currency, Optional<BigDecimal> minAmount, Optional<BigDecimal> maxAmount, Set<String> transactionHash) {
48-
49-
List<ExtractionTransactionItemView> list = transactionItemRepositoryImpl.findByItemAccountDate(orgId, dateFrom, dateTo, event, currency, minAmount, maxAmount, transactionHash).stream().map(item -> enrichTransactionItemViewBuilder(extractionTransactionItemViewBuilder(item))).toList();
50-
51-
// aggregating in case there are duplicate items due to the enrichment process it is possible to have newly duplicates
52-
return list.stream()
53-
.collect(Collectors.groupingBy(ExtractionTransactionItemView::aggregationHashCode, Collectors.toSet()))
54-
.values().stream()
55-
.map(itemSet -> {
56-
ExtractionTransactionItemView aggregatedItem = itemSet.iterator().next();
57-
aggregatedItem.setAmountFcy(itemSet.stream()
58-
.map(ExtractionTransactionItemView::getAmountFcy)
59-
.reduce(ZERO, BigDecimal::add));
60-
aggregatedItem.setAmountLcy(itemSet.stream()
61-
.map(ExtractionTransactionItemView::getAmountLcy)
62-
.reduce(ZERO, BigDecimal::add));
63-
return aggregatedItem;
64-
})
65-
.toList();
46+
public ExtractionTransactionView findTransactionItemsPublic(String orgId, LocalDate dateFrom, LocalDate dateTo, Set<String> event, Set<String> currency, Optional<BigDecimal> minAmount, Optional<BigDecimal> maxAmount, Set<String> transactionHash, int page, int limit) {
47+
48+
List<ExtractionTransactionItemView> transactionItemViews = transactionItemRepositoryImpl.findByItemAccountDateAggregated(orgId, dateFrom, dateTo, event, currency, minAmount, maxAmount, transactionHash, page, limit).stream().map(item -> enrichTransactionItemViewBuilder(extractionTransactionItemViewBuilder(item))).toList();
49+
long countTotalElements = transactionItemRepositoryImpl.countItemsByAccountDateAggregated(orgId, dateFrom, dateTo, event, currency, minAmount, maxAmount, transactionHash);
50+
return ExtractionTransactionView.createSuccess(transactionItemViews, countTotalElements, page, limit);
6651
}
6752

6853
private ExtractionTransactionItemView extractionTransactionItemViewBuilder(TransactionItemEntity item) {

accounting_reporting_core/src/main/java/org/cardanofoundation/lob/app/accounting_reporting_core/resource/views/ExtractionTransactionView.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,21 @@ public class ExtractionTransactionView {
2121

2222
private Optional<Problem> error;
2323

24-
public static ExtractionTransactionView createSuccess(List<ExtractionTransactionItemView> transactions) {
24+
private int page;
25+
private int size;
26+
27+
public static ExtractionTransactionView createSuccess(List<ExtractionTransactionItemView> transactions, long totalElements, int page, int limit) {
2528
return new ExtractionTransactionView(
2629
true,
27-
transactions.stream().count(),
30+
totalElements,
2831
transactions,
29-
Optional.empty()
32+
Optional.empty(),
33+
page,
34+
limit
3035
);
3136
}
3237

3338
public static ExtractionTransactionView createFail(Problem error) {
34-
return new ExtractionTransactionView(false, 0L, List.of(), Optional.of(error));
39+
return new ExtractionTransactionView(false, 0L, List.of(), Optional.of(error), 0, 0);
3540
}
3641
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.cardanofoundation.lob.app.accounting_reporting_core.resource.views;
2+
3+
import java.math.BigDecimal;
4+
5+
import lombok.AllArgsConstructor;
6+
import lombok.Getter;
7+
import lombok.Setter;
8+
9+
@Getter
10+
@Setter
11+
@AllArgsConstructor
12+
public class TransactionItemAggregateView {
13+
14+
private String txId;
15+
private BigDecimal amountLcyAggregated;
16+
private BigDecimal amountFcyAggregated;
17+
18+
}

accounting_reporting_core/src/test/java/org/cardanofoundation/lob/app/accounting_reporting_core/repository/TransactionItemExtractionRepositoryTest.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ class TransactionItemExtractionRepositoryTest {
3030

3131
@Mock
3232
private EntityManager em;
33+
@Mock
34+
private TransactionItemRepository transactionItemRepository;
3335

3436
@Test
3537
void findByItemAccountOnlyDates() {
3638
TypedQuery queryResult = Mockito.mock(TypedQuery.class);
37-
TransactionItemExtractionRepository transactionItemExtractionRepository = new TransactionItemExtractionRepository(em);
39+
TransactionItemExtractionRepository transactionItemExtractionRepository = new TransactionItemExtractionRepository(em, transactionItemRepository);
3840

3941
Mockito.when(em.createQuery(anyString(), eq(TransactionItemEntity.class))).thenReturn(queryResult);
4042
transactionItemExtractionRepository.findByItemAccount(
@@ -97,18 +99,19 @@ String normalize(String input) {
9799
@Test
98100
void findByItemAccountDate() {
99101
jakarta.persistence.Query queryResult = Mockito.mock(Query.class);
100-
TransactionItemExtractionRepository transactionItemExtractionRepository = new TransactionItemExtractionRepository(em);
102+
TransactionItemExtractionRepository transactionItemExtractionRepository = new TransactionItemExtractionRepository(em, transactionItemRepository);
101103

102104
Mockito.when(em.createQuery(anyString())).thenReturn(queryResult);
103-
transactionItemExtractionRepository.findByItemAccountDate(
105+
transactionItemExtractionRepository.findByItemAccountDateAggregated(
104106
"OrgId",
105107
LocalDate.of(2023, Month.JANUARY, 1),
106108
LocalDate.of(2023, Month.JANUARY, 31),
107109
Set.of("EventCode2", "EventCode1"),
108110
Set.of("Currency2", "Currency1"),
109111
Optional.of(BigDecimal.valueOf(100)),
110112
Optional.of(BigDecimal.valueOf(1000)),
111-
Set.of("TheHast2", "TheHast1")
113+
Set.of("TheHast2", "TheHast1"),
114+
0, 10
112115
);
113116
Mockito.verify(em, Mockito.times(1)).createQuery(anyString());
114117
}

0 commit comments

Comments
 (0)