package com.kaidi.oa.web;
import com.kaidi.oa.common.ApiException;
import com.kaidi.oa.common.ApiResp;
import com.kaidi.oa.common.Money;
import com.kaidi.oa.common.NotFoundException;
import com.kaidi.oa.domain.LegalCreditInvest;
import com.kaidi.oa.repository.ContractRepository;
import com.kaidi.oa.repository.CustomerCreditRepository;
import com.kaidi.oa.repository.CustomerRepository;
import com.kaidi.oa.repository.LegalCreditInvestRepository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 法务专项客户信用调查端点(Module 10 协同缺口:
* 法务「客户信用调查」仅有 CustomerCredit 台账,无法务专项信用调查端点)。
*
*
本控制器暴露 {@code /api/oa/legal-credit-invest} 路由,补全法务风险部对客户的专项信用调查能力:
*
* - 法务专项调查记录 CRUD(独立于市场部授信档案,专注合规/诉讼/失信/调查意见)
* - 关联 CustomerCredit 授信档案(聚合客户信用全景)
* - {@code GET /api/oa/legal-credit-invest/aggregate?customerId=} — 法务视角信用全景:授信档案+调查记录+合同履约
* - {@code POST /api/oa/legal-credit-invest/{id}/conclude} — 调查结论(通过/驳回/需进一步调查)
*
*
* 调查报告含客户敏感财务信息,读口已加入 AuthInterceptor SENSITIVE_READ_PREFIXES(见 sharedFileSnippets)。
*/
@RestController
@RequestMapping("/api/oa/legal-credit-invest")
public class LegalCreditInvestController {
private final LegalCreditInvestRepository investRepo;
private final CustomerCreditRepository creditRepo;
private final CustomerRepository customerRepo;
private final ContractRepository contractRepo;
public LegalCreditInvestController(LegalCreditInvestRepository investRepo,
CustomerCreditRepository creditRepo,
CustomerRepository customerRepo,
ContractRepository contractRepo) {
this.investRepo = investRepo;
this.creditRepo = creditRepo;
this.customerRepo = customerRepo;
this.contractRepo = contractRepo;
}
// ---------- 调查记录 CRUD ----------
@GetMapping
public ApiResp> list(
@RequestParam(required = false) Long customerId,
@RequestParam(required = false) String status,
@RequestParam(required = false) String keyword) {
if (customerId != null) {
return ApiResp.ok(investRepo.findByCustomerId(customerId));
}
if (status != null && !status.isBlank()) {
return ApiResp.ok(investRepo.findByStatus(status));
}
if (keyword != null && !keyword.isBlank()) {
return ApiResp.ok(investRepo.findByCustomerNameContaining(keyword));
}
return ApiResp.ok(investRepo.findAll());
}
@GetMapping("/{id}")
public ApiResp get(@PathVariable Long id) {
return ApiResp.ok(load(id));
}
public record InvestRequest(Long customerId, String customerName,
String investigationType, String scope,
String courtRecord, Boolean blacklisted,
String litigationHistory, String creditScore,
String investigator, String targetCompletionDate,
String remark) {
}
@PostMapping
@Transactional
public ApiResp create(@RequestBody InvestRequest req) {
if (req.customerId() == null && (req.customerName() == null || req.customerName().isBlank())) {
throw new ApiException(400, "客户ID(customerId)或客户名称(customerName)至少填写一项");
}
LegalCreditInvest inv = new LegalCreditInvest();
String custName = req.customerName();
if (custName == null || custName.isBlank()) {
custName = customerRepo.findById(req.customerId())
.map(c -> c.getName())
.orElse("(客户" + req.customerId() + ")");
}
inv.setCustomerId(req.customerId());
inv.setCustomerName(custName);
inv.setInvestigationType(req.investigationType() == null ? "综合调查" : req.investigationType());
inv.setScope(req.scope());
inv.setCourtRecord(req.courtRecord());
inv.setBlacklisted(Boolean.TRUE.equals(req.blacklisted()));
inv.setLitigationHistory(req.litigationHistory());
inv.setCreditScore(req.creditScore());
inv.setInvestigator(req.investigator());
inv.setTargetCompletionDate(req.targetCompletionDate());
inv.setRemark(req.remark());
inv.setStatus("调查中");
inv.setCreatedAt(Instant.now());
return ApiResp.ok(investRepo.save(inv));
}
@PatchMapping("/{id}")
@Transactional
public ApiResp update(@PathVariable Long id, @RequestBody InvestRequest req) {
LegalCreditInvest inv = load(id);
if (req.investigationType() != null) inv.setInvestigationType(req.investigationType());
if (req.scope() != null) inv.setScope(req.scope());
if (req.courtRecord() != null) inv.setCourtRecord(req.courtRecord());
if (req.blacklisted() != null) inv.setBlacklisted(req.blacklisted());
if (req.litigationHistory() != null) inv.setLitigationHistory(req.litigationHistory());
if (req.creditScore() != null) inv.setCreditScore(req.creditScore());
if (req.investigator() != null) inv.setInvestigator(req.investigator());
if (req.targetCompletionDate() != null) inv.setTargetCompletionDate(req.targetCompletionDate());
if (req.remark() != null) inv.setRemark(req.remark());
return ApiResp.ok(investRepo.save(inv));
}
@DeleteMapping("/{id}")
@Transactional
public ApiResp delete(@PathVariable Long id) {
if (!investRepo.existsById(id)) {
throw new NotFoundException("法务调查记录不存在:" + id);
}
investRepo.deleteById(id);
return ApiResp.ok(null);
}
// ---------- 调查结论 ----------
public record ConcludeRequest(String conclusion, String opinion, String conclusionDate) {
}
/**
* POST /{id}/conclude — 调查结论(通过 / 驳回 / 需进一步调查)。
* 结论为"驳回"时:关联 CustomerCredit 自动追加法务风险警示 remark,供合同审批联动参考。
*/
@PostMapping("/{id}/conclude")
@Transactional
public ApiResp conclude(@PathVariable Long id, @RequestBody ConcludeRequest req) {
LegalCreditInvest inv = load(id);
if ("已结案".equals(inv.getStatus())) {
throw new ApiException(409, "该调查已结案,不可重复结案");
}
List validConclusions = List.of("通过", "驳回", "需进一步调查");
if (req.conclusion() == null || !validConclusions.contains(req.conclusion())) {
throw new ApiException(400, "结论(conclusion)必须为:通过/驳回/需进一步调查");
}
inv.setConclusion(req.conclusion());
inv.setConclusionOpinion(req.opinion());
inv.setConclusionDate(req.conclusionDate());
inv.setStatus("已结案");
// 驳回时:若有关联授信档案,追加法务风险警示标记
if ("驳回".equals(req.conclusion()) && inv.getCustomerId() != null) {
creditRepo.findByCustomerId(inv.getCustomerId()).forEach(cc -> {
String existRemark = cc.getRemark() == null ? "" : cc.getRemark();
if (!existRemark.contains("[法务风险警示]")) {
String warn = req.opinion() != null ? req.opinion() : "法务审查驳回";
cc.setRemark(existRemark + " [法务风险警示:" + warn + "]");
creditRepo.save(cc);
}
});
}
return ApiResp.ok(investRepo.save(inv));
}
// ---------- 法务视角信用全景(聚合)----------
/**
* GET /aggregate?customerId=&customerName= — 法务视角客户信用全景:
* 授信档案 + 法务调查记录 + 合同履约概况,统一聚合,无需手动跨控制器取数(补完 PARTIAL 缺口)。
*/
@GetMapping("/aggregate")
public ApiResp