wenminjie
2026-08-12 7361c250cee0a13724bd4ac613a39e57b8576ec5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package com.talentcard.server.business.portal;
 
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
 
import com.talentcard.server.business.DatabaseRows;
import com.talentcard.server.business.PageResult;
import com.talentcard.server.common.BusinessException;
import com.talentcard.server.common.ErrorCode;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
@Service
public class UsageRecordPortalService {
 
    private static final List<String> SUPPORTED_STATUSES = List.of(
            "pending_payment", "pending_verification", "completed", "cancelled", "processing", "received"
    );
 
    private final JdbcTemplate jdbcTemplate;
 
    public UsageRecordPortalService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
 
    @Transactional(readOnly = true)
    public PageResult<Map<String, Object>> list(Long talentId, String status, int page, int size) {
        if (talentId == null || talentId <= 0) {
            throw new BusinessException(ErrorCode.BAD_REQUEST, "talentId不能为空");
        }
        if (page < 1 || size < 1 || size > 50) {
            throw new BusinessException(ErrorCode.BAD_REQUEST, "分页参数不正确");
        }
        if (status != null && !status.isBlank() && !SUPPORTED_STATUSES.contains(status)) {
            throw new BusinessException(ErrorCode.BAD_REQUEST, "不支持的记录状态");
        }
        Integer talentCount = jdbcTemplate.queryForObject(
                "select count(*) from talent_user where id = ? and deleted = 0", Integer.class, talentId);
        if (talentCount == null || talentCount == 0) {
            throw new BusinessException(ErrorCode.NOT_FOUND, "人才用户不存在");
        }
 
        String sourceSql = sourceSql();
        String where = status == null || status.isBlank() ? "" : " where usage_record.status = ?";
        List<Object> arguments = sourceArguments(talentId);
        if (!where.isEmpty()) arguments.add(status);
        Long total = jdbcTemplate.queryForObject(
                "select count(*) from (" + sourceSql + ") usage_record" + where,
                Long.class,
                arguments.toArray());
 
        List<Object> pageArguments = new ArrayList<>(arguments);
        pageArguments.add(size);
        pageArguments.add((page - 1) * size);
        String pageSql = "select * from (" + sourceSql + ") usage_record" + where
                + " order by usage_record.occurred_at desc, usage_record.record_id desc limit ? offset ?";
        List<Map<String, Object>> records = jdbcTemplate.queryForList(pageSql, pageArguments.toArray()).stream()
                .map(DatabaseRows::normalize)
                .map(this::enrichRecord)
                .toList();
        return new PageResult<>(records, page, size, total == null ? 0 : total);
    }
 
    private List<Object> sourceArguments(Long talentId) {
        return new ArrayList<>(List.of(talentId, talentId, talentId, talentId));
    }
 
    private String sourceSql() {
        return """
            select concat('order-', o.id) as record_id,
                   'ORDER' as record_type,
                   o.order_no as business_id,
                   o.id as target_detail_id,
                   o.merchant_id,
                       coalesce(nullif(m.merchant_name, ''), '人才权益服务') as merchant_name,
                       coalesce(nullif(m.merchant_type, ''), 'service') as merchant_type,
                       coalesce(nullif(b.benefit_name, ''), o.order_type, '人才权益订单') as detail_name,
                       o.created_at as occurred_at,
                       case
                         when o.order_status = 0 then 'pending_payment'
                         when o.order_status = 2 then 'cancelled'
                         when o.verify_status = 1 then 'completed'
                         else 'pending_verification'
                       end as status,
                       o.pay_amount as amount_cent,
                       'ORDER_DETAIL' as target_detail_type
                from benefit_order o
                left join merchant m on m.id = o.merchant_id
                left join benefit b on b.id = o.benefit_id
                where o.deleted = 0 and o.talent_id = ?
                union all
            select concat('service-', s.id) as record_id,
                   'SERVICE_APPLICATION' as record_type,
                   s.application_no as business_id,
                   s.id as target_detail_id,
                   null as merchant_id,
                       coalesce(nullif(s.merchant_name, ''), nullif(s.hospital_name, ''), nullif(s.parking_lot, ''), '人才服务') as merchant_name,
                       case
                         when s.application_type like 'MEDICAL%' then 'medical'
                         when s.application_type like 'HOTEL%' then 'hotel'
                         when s.application_type like 'PARKING%' then 'parking'
                         when s.application_type like 'FUEL%' then 'fuel'
                         else 'service'
                       end as merchant_type,
                       coalesce(nullif(s.product_name, ''), nullif(s.package_name, ''), s.application_type) as detail_name,
                       s.submitted_at as occurred_at,
                       case
                         when s.status in ('已处理', '已完成') then 'completed'
                         when s.status in ('已驳回', '已取消') then 'cancelled'
                         else 'processing'
                       end as status,
                       null as amount_cent,
                       'SERVICE_APPLICATION_DETAIL' as target_detail_type
                from service_application s
                where s.deleted = 0
                  and exists (
                    select 1 from talent_card c
                    where c.talent_id = ? and c.deleted = 0 and c.card_type = 'PERSONAL' and c.card_no = s.card_no
                  )
                union all
                select concat('movie-', r.id) as record_id,
                       'MOVIE_TICKET' as record_type,
                       r.receive_no as business_id,
                       cc.id as target_detail_id,
                       null as merchant_id,
                       '万达影城' as merchant_name,
                       'cinema' as merchant_type,
                       r.package_name as detail_name,
                       r.received_at as occurred_at,
                       'received' as status,
                       null as amount_cent,
                       'MOVIE_TICKET_DETAIL' as target_detail_type
                from movie_ticket_record r
                left join cinema_coupon_claim cc on cc.claim_no = r.receive_no and cc.deleted = 0
                where r.deleted = 0
                  and exists (
                    select 1 from talent_card c
                    where c.talent_id = ? and c.deleted = 0 and c.card_type = 'PERSONAL' and c.card_no = r.card_no
                  )
                union all
                select concat('ticket-package-', cl.id) as record_id,
                       'MOVIE_TICKET' as record_type,
                       cl.claim_no as business_id,
                       cl.id as target_detail_id,
                       cl.merchant_id,
                       coalesce(m.merchant_name, (
                           select m0.merchant_name
                           from ticket_package_merchant pm0
                           join merchant m0 on m0.id = pm0.merchant_id and m0.deleted = 0
                           where pm0.package_id = p.id and pm0.deleted = 0
                           order by pm0.sort_no asc, pm0.id asc
                           limit 1
                       ), '电影票兑换券') as merchant_name,
                       'cinema' as merchant_type,
                       p.package_name as detail_name,
                       cl.created_at as occurred_at,
                       'received' as status,
                       null as amount_cent,
                       'TICKET_PACKAGE_DETAIL' as target_detail_type
                from ticket_package_claim cl
                join ticket_package p on p.id = cl.package_id and p.deleted = 0
                left join merchant m on m.id = cl.merchant_id and m.deleted = 0
                where cl.deleted = 0 and cl.talent_id = ?
                """;
    }
 
    private Map<String, Object> enrichRecord(Map<String, Object> record) {
        Object amountCent = record.remove("amountCent");
        if (amountCent instanceof Number number) {
            record.put("amount", BigDecimal.valueOf(number.longValue(), 2));
        } else {
            record.put("amount", null);
        }
        record.put("merchantType", normalizedMerchantType(String.valueOf(record.getOrDefault("merchantType", "service"))));
        return record;
    }
 
    private String normalizedMerchantType(String merchantType) {
        return switch (merchantType) {
            case "food", "餐饮服务" -> "food";
            case "hotel", "酒店", "公寓" -> "hotel";
            case "cinema", "影院" -> "cinema";
            case "theatre", "剧院" -> "theatre";
            case "medical", "医疗" -> "medical";
            case "scenic", "景区" -> "scenic";
            case "parking", "停车" -> "parking";
            case "fuel", "加油" -> "fuel";
            case "carWash", "洗车" -> "carWash";
            case "shopping", "购物" -> "shopping";
            default -> "service";
        };
    }
}