zhangwencui
2026-10-29 b2f8320707dc72b2429bfb4fe0b317c11b1e5b7f
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
package com.ruoyi.basic.service;
 
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.mapper.CustomerFinancialReportMapper;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.mapper.CustomerUserMapper;
import com.ruoyi.basic.mapper.StorageBlobMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.CustomerFinancialReport;
import com.ruoyi.basic.pojo.CustomerUser;
import com.ruoyi.basic.pojo.StorageBlob;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.security.LoginUser;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
import java.time.LocalDateTime;
import java.time.Year;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
 
/**
 * 客户近3年财务报表Service
 */
@Service
@RequiredArgsConstructor
public class CustomerFinancialReportService extends ServiceImpl<CustomerFinancialReportMapper, CustomerFinancialReport> {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
 
    private final CustomerMapper customerMapper;
    private final CustomerUserMapper customerUserMapper;
    private final StorageBlobMapper storageBlobMapper;
    private final FileUtil fileUtil;
 
    /**
     * 后台按客户查询近3年财务报表
     */
    public List<CustomerFinancialReport> listByCustomer(Long customerId) {
        if (customerId == null) {
            throw new ServiceException("请选择客户");
        }
        List<CustomerFinancialReport> reports = list(new LambdaQueryWrapper<CustomerFinancialReport>()
                .eq(CustomerFinancialReport::getCustomerId, customerId)
                .orderByDesc(CustomerFinancialReport::getReportYear));
        refreshFileUrls(reports);
        fillCustomerName(reports);
        return reports;
    }
 
    /**
     * 客户账号查询自己的近3年财务报表
     */
    public List<CustomerFinancialReport> listMine() {
        Long customerId = currentCustomerId();
        List<CustomerFinancialReport> reports = list(new LambdaQueryWrapper<CustomerFinancialReport>()
                .eq(CustomerFinancialReport::getCustomerId, customerId)
                .orderByDesc(CustomerFinancialReport::getReportYear));
        refreshFileUrls(reports);
        fillCustomerName(reports);
        return reports;
    }
 
    /**
     * 保存财务报表:同一客户同一年度仅保留一条,可反复覆盖修改
     */
    @Transactional(rollbackFor = Exception.class)
    public CustomerFinancialReport saveReport(CustomerFinancialReport input) {
        if (input.getReportYear() == null) {
            throw new ServiceException("请选择报表年度");
        }
        int currentYear = Year.now().getValue();
        if (input.getReportYear() < currentYear - 2 || input.getReportYear() > currentYear) {
            throw new ServiceException("只能上传最近三个年度的财务报表");
        }
        if (StringUtils.isBlank(input.getReportFiles())) {
            throw new ServiceException("请上传财务报表附件");
        }
 
        Long customerId;
        if (isCustomerAccount()) {
            customerId = currentCustomerId();
        } else {
            customerId = input.getCustomerId();
            if (customerId == null || customerMapper.selectById(customerId) == null) {
                throw new ServiceException("客户不存在");
            }
        }
 
        CustomerFinancialReport entity = getOne(new LambdaQueryWrapper<CustomerFinancialReport>()
                .eq(CustomerFinancialReport::getCustomerId, customerId)
                .eq(CustomerFinancialReport::getReportYear, input.getReportYear()), false);
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (entity == null) {
            entity = new CustomerFinancialReport();
            entity.setCustomerId(customerId);
            entity.setReportYear(input.getReportYear());
        }
        entity.setReportName(input.getReportName());
        entity.setReportFiles(input.getReportFiles());
        // 重新提交后回到待审批状态,等待内部人员重新审批
        entity.setAuditStatus(0);
        entity.setAuditUserId(null);
        entity.setAuditUserName(null);
        entity.setAuditTime(null);
        entity.setAuditRemark(null);
        entity.setUploadUserId(loginUser.getUserId());
        entity.setUploadUserName(loginUser.getNickName());
        saveOrUpdate(entity);
        return entity;
    }
 
    /**
     * 申报审批列表:内部人员查看所有客户的提交情况
     */
    public List<CustomerFinancialReport> auditList(String customerName, Integer reportYear, Integer auditStatus) {
        LambdaQueryWrapper<CustomerFinancialReport> wrapper = new LambdaQueryWrapper<CustomerFinancialReport>()
                .eq(reportYear != null, CustomerFinancialReport::getReportYear, reportYear)
                .eq(auditStatus != null, CustomerFinancialReport::getAuditStatus, auditStatus)
                .orderByDesc(CustomerFinancialReport::getUpdateTime);
        if (StringUtils.isNotBlank(customerName)) {
            List<Long> customerIds = customerMapper.selectList(new LambdaQueryWrapper<Customer>()
                            .like(Customer::getCustomerName, customerName))
                    .stream().map(Customer::getId).filter(Objects::nonNull).collect(Collectors.toList());
            if (customerIds.isEmpty()) {
                return new java.util.ArrayList<>();
            }
            wrapper.in(CustomerFinancialReport::getCustomerId, customerIds);
        }
        List<CustomerFinancialReport> reports = list(wrapper);
        refreshFileUrls(reports);
        fillCustomerName(reports);
        return reports;
    }
 
    /**
     * 审批财务报表:1通过 2退回
     */
    @Transactional(rollbackFor = Exception.class)
    public CustomerFinancialReport auditReport(Long id, Integer auditStatus, String auditRemark) {
        if (isCustomerAccount()) {
            throw new ServiceException("客户账号无权审批财务报表");
        }
        if (id == null) {
            throw new ServiceException("请选择申报记录");
        }
        if (auditStatus == null || auditStatus != 1 && auditStatus != 2) {
            throw new ServiceException("审批结果不正确");
        }
        CustomerFinancialReport entity = getById(id);
        if (entity == null) {
            throw new ServiceException("申报记录不存在");
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        entity.setAuditStatus(auditStatus);
        entity.setAuditUserId(loginUser.getUserId());
        entity.setAuditUserName(loginUser.getNickName());
        entity.setAuditTime(LocalDateTime.now());
        entity.setAuditRemark(auditRemark);
        updateById(entity);
        return entity;
    }
 
    /**
     * 删除财务报表:仅后台人员可删除,客户账号不允许删除
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteReport(Long id) {
        if (isCustomerAccount()) {
            throw new ServiceException("客户账号无权删除财务报表");
        }
        CustomerFinancialReport entity = getById(id);
        if (entity == null) {
            throw new ServiceException("财务报表不存在");
        }
        removeById(id);
    }
 
    /**
     * 重新生成附件预览/下载地址:数据库中保存的是上传时的带签名地址,会过期,读取时按文件id刷新
     */
    private void refreshFileUrls(List<CustomerFinancialReport> reports) {
        if (reports == null || reports.isEmpty()) {
            return;
        }
        for (CustomerFinancialReport report : reports) {
            String files = report.getReportFiles();
            if (StringUtils.isBlank(files)) {
                continue;
            }
            try {
                JsonNode root = OBJECT_MAPPER.readTree(files);
                if (root == null || !root.isArray()) {
                    continue;
                }
                for (JsonNode node : root) {
                    Long blobId = node.hasNonNull("id") ? node.get("id").asLong() : null;
                    if (blobId == null) {
                        continue;
                    }
                    StorageBlob blob = storageBlobMapper.selectById(blobId);
                    if (blob == null) {
                        continue;
                    }
                    StorageBlobVO vo = new StorageBlobVO();
                    vo.setUidFilename(blob.getUidFilename());
                    vo.setPath(blob.getPath());
                    vo.setResourceKey(blob.getResourceKey());
                    ObjectNode objectNode = (ObjectNode) node;
                    objectNode.put("url", fileUtil.buildSignedPreviewUrl(vo));
                    objectNode.put("downloadURL", fileUtil.buildSignedDownloadUrl(vo));
                    if (!node.hasNonNull("name")) {
                        objectNode.put("name", blob.getOriginalFilename());
                    }
                }
                report.setReportFiles(OBJECT_MAPPER.writeValueAsString(root));
            } catch (Exception e) {
                // 旧数据或非 JSON 格式,保持原值
            }
        }
    }
 
    private boolean isCustomerAccount() {
        Long userId = SecurityUtils.getUserId();
        return !SecurityUtils.isAdmin(userId) && SecurityUtils.hasRole("customer");
    }
 
    /**
     * 获取当前登录账号绑定的客户id
     */
    private Long currentCustomerId() {
        CustomerUser binding = customerUserMapper.selectOne(new LambdaQueryWrapper<CustomerUser>()
                .eq(CustomerUser::getUserId, SecurityUtils.getUserId()).last("limit 1"));
        if (binding == null || binding.getCustomerId() == null) {
            throw new ServiceException("当前账号尚未绑定客户,请联系管理员");
        }
        return binding.getCustomerId();
    }
 
    private void fillCustomerName(List<CustomerFinancialReport> reports) {
        if (reports == null || reports.isEmpty()) {
            return;
        }
        Set<Long> ids = reports.stream().map(CustomerFinancialReport::getCustomerId)
                .filter(Objects::nonNull).collect(Collectors.toSet());
        if (ids.isEmpty()) {
            return;
        }
        List<Customer> customers = customerMapper.selectBatchIds(ids);
        java.util.Map<Long, String> nameMap = customers.stream()
                .collect(Collectors.toMap(Customer::getId, Customer::getCustomerName, (a, b) -> a));
        reports.forEach(item -> item.setCustomerName(nameMap.getOrDefault(item.getCustomerId(), "")));
    }
}