PaymentLedgerService.java

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

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

Active mutators

Tests examined


Report generated by PIT 1.30.0 support