|
1
|
|
package com.rbctcsworld.ecommerce.report; |
|
2
|
|
|
|
3
|
|
import com.rbctcsworld.ecommerce.common.exception.BusinessRuleException; |
|
4
|
|
import org.springframework.jdbc.core.JdbcTemplate; |
|
5
|
|
import org.springframework.jdbc.core.RowMapper; |
|
6
|
|
import org.springframework.stereotype.Service; |
|
7
|
|
import org.springframework.transaction.annotation.Transactional; |
|
8
|
|
|
|
9
|
|
import java.math.BigDecimal; |
|
10
|
|
import java.sql.Timestamp; |
|
11
|
|
import java.time.Clock; |
|
12
|
|
import java.time.LocalDate; |
|
13
|
|
import java.time.LocalDateTime; |
|
14
|
|
import java.time.temporal.ChronoUnit; |
|
15
|
|
import java.util.ArrayList; |
|
16
|
|
import java.util.List; |
|
17
|
|
import java.util.Locale; |
|
18
|
|
|
|
19
|
|
/** |
|
20
|
|
* Every charge and refund (payment_transactions), newest first, for the store admin: the money trail |
|
21
|
|
* behind the orders. Only the last 4 card digits exist, so nothing more can be shown. |
|
22
|
|
*/ |
|
23
|
|
@Service |
|
24
|
|
@Transactional(readOnly = true) |
|
25
|
|
public class PaymentLedgerService { |
|
26
|
|
|
|
27
|
|
public record Payment(long id, long orderId, String orderNumber, String type, String status, BigDecimal amount, |
|
28
|
|
String cardLast4, String provider, String failureReason, LocalDateTime createdAt) { |
|
29
|
|
} |
|
30
|
|
|
|
31
|
|
public record PaymentPage(List<Payment> items, long total, BigDecimal succeededCharges, |
|
32
|
|
BigDecimal succeededRefunds, int page, int size) { |
|
33
|
|
} |
|
34
|
|
|
|
35
|
|
private static final RowMapper<Payment> ROW = (rs, i) -> new Payment(rs.getLong("id"), rs.getLong("order_id"), |
|
36
|
|
rs.getString("order_number"), rs.getString("type"), rs.getString("status"), rs.getBigDecimal("amount"), |
|
37
|
|
rs.getString("card_last4"), rs.getString("provider"), rs.getString("failure_reason"), |
|
38
|
|
rs.getTimestamp("created_at").toLocalDateTime()); |
|
39
|
|
|
|
40
|
|
private final JdbcTemplate jdbc; |
|
41
|
|
private final Clock clock; |
|
42
|
|
|
|
43
|
|
public PaymentLedgerService(JdbcTemplate jdbc, Clock clock) { |
|
44
|
|
this.jdbc = jdbc; |
|
45
|
|
this.clock = clock; |
|
46
|
|
} |
|
47
|
|
|
|
48
|
|
/** |
|
49
|
|
* from/to: inclusive days (default: the last 30 days, at most 366); type CHARGE | REFUND; status |
|
50
|
|
* SUCCEEDED | FAILED; q = part of the order number; page from 0, size 1..100 (default 20). |
|
51
|
|
*/ |
|
52
|
|
public PaymentPage search(LocalDate from, LocalDate to, String type, String status, String q, |
|
53
|
|
Integer page, Integer size) { |
|
54
|
1
1. search : removed conditional - replaced equality check with false → KILLED
|
int p = page == null ? 0 : page; |
|
55
|
1
1. search : removed conditional - replaced equality check with false → KILLED
|
int n = size == null ? 20 : size; |
|
56
|
2
1. search : removed conditional - replaced comparison check with false → KILLED
2. search : changed conditional boundary → KILLED
|
if (p < 0) throw new BusinessRuleException("page must be 0 or more"); |
|
57
|
4
1. search : changed conditional boundary → KILLED
2. search : removed conditional - replaced comparison check with false → KILLED
3. search : changed conditional boundary → KILLED
4. search : removed conditional - replaced comparison check with false → KILLED
|
if (n < 1 || n > 100) throw new BusinessRuleException("size must be between 1 and 100"); |
|
58
|
1
1. search : removed conditional - replaced equality check with false → KILLED
|
LocalDate end = to == null ? LocalDate.now(clock) : to; |
|
59
|
1
1. search : removed conditional - replaced equality check with false → KILLED
|
LocalDate start = from == null ? end.minusDays(29) : from; |
|
60
|
1
1. search : removed conditional - replaced equality check with false → KILLED
|
if (start.isAfter(end)) throw new BusinessRuleException("from must be on or before to"); |
|
61
|
3
1. search : Replaced long addition with subtraction → KILLED
2. search : changed conditional boundary → KILLED
3. search : removed conditional - replaced comparison check with false → KILLED
|
if (ChronoUnit.DAYS.between(start, end) + 1 > ReportService.MAX_DAYS) { |
|
62
|
|
throw new BusinessRuleException("at most " + ReportService.MAX_DAYS + " days"); |
|
63
|
|
} |
|
64
|
|
|
|
65
|
|
StringBuilder where = new StringBuilder(" where t.created_at >= ? and t.created_at < ?"); |
|
66
|
|
List<Object> args = new ArrayList<>(List.of(Timestamp.valueOf(start.atStartOfDay()), |
|
67
|
|
Timestamp.valueOf(end.plusDays(1).atStartOfDay()))); |
|
68
|
2
1. search : removed conditional - replaced equality check with false → KILLED
2. search : removed conditional - replaced equality check with false → KILLED
|
if (type != null && !type.isBlank()) { |
|
69
|
|
String t = type.trim().toUpperCase(Locale.ROOT); |
|
70
|
2
1. search : removed conditional - replaced equality check with false → KILLED
2. search : removed conditional - replaced equality check with false → KILLED
|
if (!t.equals("CHARGE") && !t.equals("REFUND")) throw new BusinessRuleException("type must be CHARGE or REFUND"); |
|
71
|
|
where.append(" and t.type = ?"); |
|
72
|
|
args.add(t); |
|
73
|
|
} |
|
74
|
2
1. search : removed conditional - replaced equality check with false → KILLED
2. search : removed conditional - replaced equality check with false → KILLED
|
if (status != null && !status.isBlank()) { |
|
75
|
|
String s = status.trim().toUpperCase(Locale.ROOT); |
|
76
|
2
1. search : removed conditional - replaced equality check with false → KILLED
2. search : removed conditional - replaced equality check with false → KILLED
|
if (!s.equals("SUCCEEDED") && !s.equals("FAILED")) { |
|
77
|
|
throw new BusinessRuleException("status must be SUCCEEDED or FAILED"); |
|
78
|
|
} |
|
79
|
|
where.append(" and t.status = ?"); |
|
80
|
|
args.add(s); |
|
81
|
|
} |
|
82
|
2
1. search : removed conditional - replaced equality check with false → KILLED
2. search : removed conditional - replaced equality check with false → KILLED
|
if (q != null && !q.isBlank()) { |
|
83
|
|
where.append(" and upper(o.order_number) like ?"); |
|
84
|
|
args.add("%" + q.trim().toUpperCase(Locale.ROOT).replace("%", "").replace("_", "") + "%"); |
|
85
|
|
} |
|
86
|
|
String from_ = " from payment_transactions t join orders o on o.id = t.order_id" + where; |
|
87
|
|
Long total = jdbc.queryForObject("select count(*)" + from_, Long.class, args.toArray()); |
|
88
|
|
BigDecimal charges = jdbc.queryForObject("select coalesce(sum(t.amount), 0)" + from_ |
|
89
|
|
+ " and t.type = 'CHARGE' and t.status = 'SUCCEEDED'", BigDecimal.class, args.toArray()); |
|
90
|
|
BigDecimal refunds = jdbc.queryForObject("select coalesce(sum(t.amount), 0)" + from_ |
|
91
|
|
+ " and t.type = 'REFUND' and t.status = 'SUCCEEDED'", BigDecimal.class, args.toArray()); |
|
92
|
1
1. search : Replaced long multiplication with division → KILLED
|
List<Payment> items = jdbc.query("select t.id, t.order_id, o.order_number, t.type, t.status, t.amount, " |
|
93
|
|
+ "t.card_last4, t.provider, t.failure_reason, t.created_at" + from_ |
|
94
|
|
+ " order by t.created_at desc, t.id desc limit " + n + " offset " + ((long) p * n), ROW, args.toArray()); |
|
95
|
2
1. search : removed conditional - replaced equality check with false → SURVIVED
2. search : replaced return value with null for com/rbctcsworld/ecommerce/report/PaymentLedgerService::search → KILLED
|
return new PaymentPage(items, total == null ? 0 : total, money(charges), money(refunds), p, n); |
|
96
|
|
} |
|
97
|
|
|
|
98
|
|
private static BigDecimal money(BigDecimal v) { |
|
99
|
2
1. money : removed conditional - replaced equality check with false → SURVIVED
2. money : replaced return value with null for com/rbctcsworld/ecommerce/report/PaymentLedgerService::money → KILLED
|
return (v == null ? BigDecimal.ZERO : v).setScale(2, java.math.RoundingMode.HALF_UP); |
|
100
|
|
} |
|
101
|
|
} |
| | Mutations |
| 54 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:newestFirstWithTheSumsOfWhatReallyMoved()] removed conditional - replaced equality check with false → KILLED
|
| 55 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:newestFirstWithTheSumsOfWhatReallyMoved()] removed conditional - replaced equality check with false → KILLED
|
| 56 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:wrongInputIsRefused()] removed conditional - replaced comparison check with false → KILLED
2.2 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:newestFirstWithTheSumsOfWhatReallyMoved()] changed conditional boundary → KILLED
|
| 57 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:wrongInputIsRefused()] changed conditional boundary → KILLED
2.2 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:wrongInputIsRefused()] removed conditional - replaced comparison check with false → KILLED
3.3 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:theDefaultIsTheLast30DaysAndPagesAreStable()] changed conditional boundary → KILLED
4.4 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:newestFirstWithTheSumsOfWhatReallyMoved()] removed conditional - replaced comparison check with false → KILLED
|
| 58 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:theDefaultIsTheLast30DaysAndPagesAreStable()] removed conditional - replaced equality check with false → KILLED
|
| 59 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:theDefaultIsTheLast30DaysAndPagesAreStable()] removed conditional - replaced equality check with false → KILLED
|
| 60 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:wrongInputIsRefused()] removed conditional - replaced equality check with false → KILLED
|
| 61 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:wrongInputIsRefused()] Replaced long addition with subtraction → KILLED
2.2 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:wrongInputIsRefused()] changed conditional boundary → KILLED
3.3 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:wrongInputIsRefused()] removed conditional - replaced comparison check with false → KILLED
|
| 68 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:filtersCombine()] removed conditional - replaced equality check with false → KILLED
2.2 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:filtersCombine()] removed conditional - replaced equality check with false → KILLED
|
| 70 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:wrongInputIsRefused()] removed conditional - replaced equality check with false → KILLED
2.2 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:wrongInputIsRefused()] removed conditional - replaced equality check with false → KILLED
|
| 74 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:filtersCombine()] removed conditional - replaced equality check with false → KILLED
2.2 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:filtersCombine()] removed conditional - replaced equality check with false → KILLED
|
| 76 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:wrongInputIsRefused()] removed conditional - replaced equality check with false → KILLED
2.2 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:wrongInputIsRefused()] removed conditional - replaced equality check with false → KILLED
|
| 82 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:filtersCombine()] removed conditional - replaced equality check with false → KILLED
2.2 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:filtersCombine()] removed conditional - replaced equality check with false → KILLED
|
| 92 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:theDefaultIsTheLast30DaysAndPagesAreStable()] Replaced long multiplication with division → KILLED
|
| 95 |
|
1.1 Location : search Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:newestFirstWithTheSumsOfWhatReallyMoved()] replaced return value with null for com/rbctcsworld/ecommerce/report/PaymentLedgerService::search → KILLED
2.2 Location : search Killed by : none removed conditional - replaced equality check with false → SURVIVED
Covering tests
Covered by tests:
- com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:newestFirstWithTheSumsOfWhatReallyMoved()]
- com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:theDefaultIsTheLast30DaysAndPagesAreStable()]
- com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:filtersCombine()]
- com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:wrongInputIsRefused()]
|
| 99 |
|
1.1 Location : money Killed by : com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:newestFirstWithTheSumsOfWhatReallyMoved()] replaced return value with null for com/rbctcsworld/ecommerce/report/PaymentLedgerService::money → KILLED
2.2 Location : money Killed by : none removed conditional - replaced equality check with false → SURVIVED
Covering tests
Covered by tests:
- com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:newestFirstWithTheSumsOfWhatReallyMoved()]
- com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:theDefaultIsTheLast30DaysAndPagesAreStable()]
- com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:filtersCombine()]
- com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.report.PaymentLedgerServiceTest]/[method:wrongInputIsRefused()]
|