汇率管理

This commit is contained in:
luojiaquan
2026-09-14 17:12:16 +08:00
parent c89c1340b0
commit aec5be9643
13 changed files with 1550 additions and 1 deletions
@@ -0,0 +1,163 @@
package com.ruoyi.web.controller.data;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
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 com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.domain.BmsExchangeRate;
import com.ruoyi.system.domain.BmsExchangeRateConvertResult;
import com.ruoyi.system.service.IBmsExchangeRateService;
/**
* 汇率管理 信息操作处理
*
* @author ruoyi
*/
@RestController
@RequestMapping("/data/exchangerate")
public class BmsExchangeRateController extends BaseController
{
@Autowired
private IBmsExchangeRateService exchangeRateService;
/**
* 获取汇率列表
*/
@PreAuthorize("@ss.hasPermi('data:exchangerate:list')")
@GetMapping("/list")
public TableDataInfo list(BmsExchangeRate exchangeRate)
{
startPage();
List<BmsExchangeRate> list = exchangeRateService.selectExchangeRateList(exchangeRate);
return getDataTable(list);
}
/**
* 导出汇率列表
*/
@PreAuthorize("@ss.hasPermi('data:exchangerate:export')")
@Log(title = "汇率管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, BmsExchangeRate exchangeRate)
{
List<BmsExchangeRate> list = exchangeRateService.selectExchangeRateList(exchangeRate);
ExcelUtil<BmsExchangeRate> util = new ExcelUtil<BmsExchangeRate>(BmsExchangeRate.class);
util.exportExcel(response, list, "汇率数据");
}
/**
* 获取汇率详细信息
*/
@PreAuthorize("@ss.hasPermi('data:exchangerate:query')")
@GetMapping(value = "/{rateId}")
public AjaxResult getInfo(@PathVariable Long rateId)
{
return success(exchangeRateService.selectExchangeRateById(rateId));
}
/**
* 新增汇率
*/
@PreAuthorize("@ss.hasPermi('data:exchangerate:add')")
@Log(title = "汇率管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody BmsExchangeRate exchangeRate)
{
exchangeRate.setCreateBy(getUsername());
return toAjax(exchangeRateService.insertExchangeRate(exchangeRate));
}
/**
* 修改汇率
*/
@PreAuthorize("@ss.hasPermi('data:exchangerate:edit')")
@Log(title = "汇率管理", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody BmsExchangeRate exchangeRate)
{
return toAjax(exchangeRateService.updateExchangeRate(exchangeRate));
}
/**
* 删除汇率
*/
@PreAuthorize("@ss.hasPermi('data:exchangerate:remove')")
@Log(title = "汇率管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{rateIds}")
public AjaxResult remove(@PathVariable Long[] rateIds)
{
return toAjax(exchangeRateService.deleteExchangeRateByIds(rateIds));
}
/**
* 汇率换算(业务公共接口,登录即可调用,不单独设权限)
*
* @param amount 源金额
* @param fromCurrency 源币种
* @param toCurrency 目标币种
* @param rateType 汇率类型(0即期 1月初 2月末 3固定),默认即期
* @param bizDate 业务日期 yyyy-MM-dd,默认当天
*/
@GetMapping("/convert")
public AjaxResult convert(@RequestParam BigDecimal amount,
@RequestParam String fromCurrency,
@RequestParam String toCurrency,
@RequestParam(required = false) String rateType,
@RequestParam(required = false) String bizDate)
{
Date date = parseDate(bizDate);
BmsExchangeRateConvertResult result = exchangeRateService.convert(
amount, fromCurrency, toCurrency, rateType, date);
return success(result);
}
/**
* 本位币(CNY)折算:任意币种金额折算为人民币
*/
@GetMapping("/toBase")
public AjaxResult toBase(@RequestParam BigDecimal amount,
@RequestParam String currency,
@RequestParam(required = false) String rateType,
@RequestParam(required = false) String bizDate)
{
Date date = parseDate(bizDate);
BmsExchangeRateConvertResult result = exchangeRateService.convert(
amount, currency, BmsExchangeRate.BASE_CURRENCY, rateType, date);
return success(result);
}
private Date parseDate(String bizDate)
{
if (bizDate == null || bizDate.isEmpty())
{
return null;
}
try
{
return new SimpleDateFormat("yyyy-MM-dd").parse(bizDate);
}
catch (Exception e)
{
return null;
}
}
}
@@ -0,0 +1,178 @@
package com.ruoyi.system.domain;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/**
* 汇率管理 sys_exchange_rate
*
* 汇率定义:1 单位源币种(from_currency) = exchange_rate 单位目标币种(to_currency)
*
* @author ruoyi
*/
public class BmsExchangeRate extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 系统本位币 */
public static final String BASE_CURRENCY = "CNY";
/** 汇率类型:即期汇率 */
public static final String RATE_TYPE_SPOT = "0";
/** 汇率类型:月初汇率 */
public static final String RATE_TYPE_MONTH_BEGIN = "1";
/** 汇率类型:月末汇率 */
public static final String RATE_TYPE_MONTH_END = "2";
/** 汇率类型:固定汇率 */
public static final String RATE_TYPE_FIXED = "3";
/** 汇率ID */
private Long rateId;
/** 源币种 */
@Excel(name = "源币种")
private String fromCurrency;
/** 目标币种 */
@Excel(name = "目标币种")
private String toCurrency;
/** 汇率类型(0即期 1月初 2月末 3固定) */
@Excel(name = "汇率类型", readConverterExp = "0=即期汇率,1=月初汇率,2=月末汇率,3=固定汇率")
private String rateType;
/** 汇率值(1源币种=?目标币种) */
@Excel(name = "汇率值")
private BigDecimal exchangeRate;
/** 生效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "生效日期", width = 30, dateFormat = "yyyy-MM-dd")
private Date effectiveDate;
/** 失效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "失效日期", width = 30, dateFormat = "yyyy-MM-dd")
private Date expireDate;
/** 汇率来源 */
@Excel(name = "汇率来源")
private String source;
/** 删除标记(0存在 1删除) */
private String delFlag;
public Long getRateId()
{
return rateId;
}
public void setRateId(Long rateId)
{
this.rateId = rateId;
}
public String getFromCurrency()
{
return fromCurrency;
}
public void setFromCurrency(String fromCurrency)
{
this.fromCurrency = fromCurrency;
}
public String getToCurrency()
{
return toCurrency;
}
public void setToCurrency(String toCurrency)
{
this.toCurrency = toCurrency;
}
public String getRateType()
{
return rateType;
}
public void setRateType(String rateType)
{
this.rateType = rateType;
}
public BigDecimal getExchangeRate()
{
return exchangeRate;
}
public void setExchangeRate(BigDecimal exchangeRate)
{
this.exchangeRate = exchangeRate;
}
public Date getEffectiveDate()
{
return effectiveDate;
}
public void setEffectiveDate(Date effectiveDate)
{
this.effectiveDate = effectiveDate;
}
public Date getExpireDate()
{
return expireDate;
}
public void setExpireDate(Date expireDate)
{
this.expireDate = expireDate;
}
public String getSource()
{
return source;
}
public void setSource(String source)
{
this.source = source;
}
public String getDelFlag()
{
return delFlag;
}
public void setDelFlag(String delFlag)
{
this.delFlag = delFlag;
}
@Override
public String toString()
{
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("rateId", getRateId())
.append("fromCurrency", getFromCurrency())
.append("toCurrency", getToCurrency())
.append("rateType", getRateType())
.append("exchangeRate", getExchangeRate())
.append("effectiveDate", getEffectiveDate())
.append("expireDate", getExpireDate())
.append("source", getSource())
.append("remark", getRemark())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("delFlag", getDelFlag())
.toString();
}
}
@@ -0,0 +1,131 @@
package com.ruoyi.system.domain;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 汇率换算结果
*
* @author ruoyi
*/
public class BmsExchangeRateConvertResult implements Serializable
{
private static final long serialVersionUID = 1L;
/** 源币种 */
private String fromCurrency;
/** 目标币种 */
private String toCurrency;
/** 汇率类型(0即期 1月初 2月末 3固定) */
private String rateType;
/** 业务日期 */
private String bizDate;
/** 使用的汇率值(1源币种=?目标币种) */
private BigDecimal rate;
/** 源金额 */
private BigDecimal amount;
/** 换算结果 */
private BigDecimal result;
/** 汇率取数路径:DIRECT 正向配置 / REVERSE 反向取倒数 / CROSS 经本位币交叉换算 / SAME 同币种 */
private String ratePath;
/** 汇率来源 */
private String source;
public String getFromCurrency()
{
return fromCurrency;
}
public void setFromCurrency(String fromCurrency)
{
this.fromCurrency = fromCurrency;
}
public String getToCurrency()
{
return toCurrency;
}
public void setToCurrency(String toCurrency)
{
this.toCurrency = toCurrency;
}
public String getRateType()
{
return rateType;
}
public void setRateType(String rateType)
{
this.rateType = rateType;
}
public String getBizDate()
{
return bizDate;
}
public void setBizDate(String bizDate)
{
this.bizDate = bizDate;
}
public BigDecimal getRate()
{
return rate;
}
public void setRate(BigDecimal rate)
{
this.rate = rate;
}
public BigDecimal getAmount()
{
return amount;
}
public void setAmount(BigDecimal amount)
{
this.amount = amount;
}
public BigDecimal getResult()
{
return result;
}
public void setResult(BigDecimal result)
{
this.result = result;
}
public String getRatePath()
{
return ratePath;
}
public void setRatePath(String ratePath)
{
this.ratePath = ratePath;
}
public String getSource()
{
return source;
}
public void setSource(String source)
{
this.source = source;
}
}
@@ -0,0 +1,45 @@
package com.ruoyi.system.mapper;
import java.util.Date;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.system.domain.BmsExchangeRate;
/**
* 汇率管理 Mapper
*
* @author ruoyi
*/
public interface BmsExchangeRateMapper
{
/** 查询汇率列表 */
public List<BmsExchangeRate> selectExchangeRateList(BmsExchangeRate exchangeRate);
/** 查询汇率详情 */
public BmsExchangeRate selectExchangeRateById(Long rateId);
/** 新增汇率 */
public int insertExchangeRate(BmsExchangeRate exchangeRate);
/** 修改汇率 */
public int updateExchangeRate(BmsExchangeRate exchangeRate);
/** 软删除汇率 */
public int deleteExchangeRateByIds(Long[] rateIds);
/**
* 查询某业务日期生效的汇率(取生效日期最新的一条)
* 只做精确正向匹配(from -> to),反向/交叉由 Service 推导
*/
public BmsExchangeRate selectEffectiveRate(@Param("fromCurrency") String fromCurrency,
@Param("toCurrency") String toCurrency,
@Param("rateType") String rateType,
@Param("bizDate") Date bizDate);
/** 校验同方向/类型/生效日期是否已存在(编辑时排除自身) */
public int countDuplicate(@Param("fromCurrency") String fromCurrency,
@Param("toCurrency") String toCurrency,
@Param("rateType") String rateType,
@Param("effectiveDate") Date effectiveDate,
@Param("excludeId") Long excludeId);
}
@@ -0,0 +1,48 @@
package com.ruoyi.system.service;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import com.ruoyi.system.domain.BmsExchangeRate;
import com.ruoyi.system.domain.BmsExchangeRateConvertResult;
/**
* 汇率管理 Service
*
* @author ruoyi
*/
public interface IBmsExchangeRateService
{
/** 查询汇率列表 */
public List<BmsExchangeRate> selectExchangeRateList(BmsExchangeRate exchangeRate);
/** 查询汇率详情 */
public BmsExchangeRate selectExchangeRateById(Long rateId);
/** 新增汇率 */
public int insertExchangeRate(BmsExchangeRate exchangeRate);
/** 修改汇率 */
public int updateExchangeRate(BmsExchangeRate exchangeRate);
/** 软删除汇率 */
public int deleteExchangeRateByIds(Long[] rateIds);
/**
* 取换算汇率:1 单位 fromCurrency = ? 单位 toCurrency
* 取数顺序:正向配置 → 反向配置取倒数 → 经本位币(CNY)交叉换算
*
* @param fromCurrency 源币种
* @param toCurrency 目标币种
* @param rateType 汇率类型(0即期 1月初 2月末 3固定),为空按即期
* @param bizDate 业务日期,为空取当天
* @return 汇率(不为 null;无配置时抛 ServiceException)
*/
public BmsExchangeRate resolveRate(String fromCurrency, String toCurrency, String rateType, Date bizDate);
/**
* 金额换算
*/
public BmsExchangeRateConvertResult convert(BigDecimal amount, String fromCurrency, String toCurrency,
String rateType, Date bizDate);
}
@@ -0,0 +1,271 @@
package com.ruoyi.system.service.impl;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.BmsExchangeRate;
import com.ruoyi.system.domain.BmsExchangeRateConvertResult;
import com.ruoyi.system.mapper.BmsExchangeRateMapper;
import com.ruoyi.system.service.IBmsExchangeRateService;
/**
* 汇率管理 Service实现
*
* @author ruoyi
*/
@Service
public class BmsExchangeRateServiceImpl implements IBmsExchangeRateService
{
/** 汇率/换算结果统一精度(8位小数) */
private static final int SCALE = 8;
@Autowired
private BmsExchangeRateMapper exchangeRateMapper;
@Override
public List<BmsExchangeRate> selectExchangeRateList(BmsExchangeRate exchangeRate)
{
return exchangeRateMapper.selectExchangeRateList(exchangeRate);
}
@Override
public BmsExchangeRate selectExchangeRateById(Long rateId)
{
return exchangeRateMapper.selectExchangeRateById(rateId);
}
@Override
public int insertExchangeRate(BmsExchangeRate exchangeRate)
{
normalize(exchangeRate);
validate(exchangeRate);
checkDuplicate(exchangeRate);
exchangeRate.setCreateBy(StringUtils.isNotEmpty(exchangeRate.getCreateBy())
? exchangeRate.getCreateBy() : "");
return exchangeRateMapper.insertExchangeRate(exchangeRate);
}
@Override
public int updateExchangeRate(BmsExchangeRate exchangeRate)
{
if (exchangeRate.getRateId() == null)
{
throw new ServiceException("汇率ID不能为空");
}
normalize(exchangeRate);
validate(exchangeRate);
checkDuplicate(exchangeRate);
return exchangeRateMapper.updateExchangeRate(exchangeRate);
}
@Override
public int deleteExchangeRateByIds(Long[] rateIds)
{
return exchangeRateMapper.deleteExchangeRateByIds(rateIds);
}
/* ============================ 汇率换算引擎 ============================ */
@Override
public BmsExchangeRate resolveRate(String fromCurrency, String toCurrency, String rateType, Date bizDate)
{
String from = normalizeCurrency(fromCurrency);
String to = normalizeCurrency(toCurrency);
String type = StringUtils.isNotEmpty(rateType) ? rateType : BmsExchangeRate.RATE_TYPE_SPOT;
Date date = bizDate != null ? bizDate : DateUtils.getNowDate();
// 同币种
if (from.equals(to))
{
return buildSyntheticRate(from, to, type, BigDecimal.ONE, date, "SAME");
}
// 1. 正向配置:from -> to
BmsExchangeRate direct = exchangeRateMapper.selectEffectiveRate(from, to, type, date);
if (direct != null)
{
return buildResult(direct, direct.getExchangeRate(), "DIRECT");
}
// 2. 反向配置:to -> from,取倒数
BmsExchangeRate reverse = exchangeRateMapper.selectEffectiveRate(to, from, type, date);
if (reverse != null && positive(reverse.getExchangeRate()))
{
return buildResult(reverse,
BigDecimal.ONE.divide(reverse.getExchangeRate(), SCALE, RoundingMode.HALF_UP),
"REVERSE");
}
// 3. 经本位币 CNY 交叉换算:from -> CNY -> to
BigDecimal toBase = lookupChainRate(from, BmsExchangeRate.BASE_CURRENCY, type, date);
BigDecimal fromBase = lookupChainRate(BmsExchangeRate.BASE_CURRENCY, to, type, date);
if (toBase != null && fromBase != null)
{
BigDecimal crossRate = toBase.multiply(fromBase).setScale(SCALE, RoundingMode.HALF_UP);
BmsExchangeRate synth = buildSyntheticRate(from, to, type, crossRate, date, "CROSS");
synth.setSource("经CNY交叉换算");
return synth;
}
throw new ServiceException("未维护生效日期内【" + from + " → " + to + "】的"
+ rateTypeName(type) + ",请先在「数据管理-汇率管理」中配置");
}
@Override
public BmsExchangeRateConvertResult convert(BigDecimal amount, String fromCurrency, String toCurrency,
String rateType, Date bizDate)
{
if (amount == null)
{
throw new ServiceException("换算金额不能为空");
}
BmsExchangeRate rate = resolveRate(fromCurrency, toCurrency, rateType, bizDate);
BmsExchangeRateConvertResult result = new BmsExchangeRateConvertResult();
result.setFromCurrency(rate.getFromCurrency());
result.setToCurrency(rate.getToCurrency());
result.setRateType(rate.getRateType());
result.setBizDate(DateUtils.parseDateToStr("yyyy-MM-dd",
bizDate != null ? bizDate : DateUtils.getNowDate()));
result.setRate(rate.getExchangeRate());
result.setAmount(amount);
result.setResult(amount.multiply(rate.getExchangeRate()).setScale(SCALE, RoundingMode.HALF_UP));
result.setSource(rate.getSource());
result.setRatePath(rate.getRemark() == null ? null : (rate.getRemark().startsWith("__RATE_PATH__")
? rate.getRemark().substring("__RATE_PATH__".length()) : "DIRECT"));
if (result.getRatePath() == null)
{
result.setRatePath("DIRECT");
}
return result;
}
/* ============================ 私有方法 ============================ */
/**
* 查找单跳汇率(先正向,再反向取倒数),查不到返回 null
*/
private BigDecimal lookupChainRate(String from, String to, String type, Date date)
{
BmsExchangeRate direct = exchangeRateMapper.selectEffectiveRate(from, to, type, date);
if (direct != null)
{
return direct.getExchangeRate();
}
BmsExchangeRate reverse = exchangeRateMapper.selectEffectiveRate(to, from, type, date);
if (reverse != null && positive(reverse.getExchangeRate()))
{
return BigDecimal.ONE.divide(reverse.getExchangeRate(), SCALE, RoundingMode.HALF_UP);
}
return null;
}
/** 基于真实记录构造返回(保留来源、日期等) */
private BmsExchangeRate buildResult(BmsExchangeRate origin, BigDecimal rate, String path)
{
BmsExchangeRate r = new BmsExchangeRate();
r.setRateId(origin.getRateId());
r.setFromCurrency(origin.getFromCurrency());
r.setToCurrency(origin.getToCurrency());
r.setRateType(origin.getRateType());
r.setExchangeRate(rate);
r.setEffectiveDate(origin.getEffectiveDate());
r.setExpireDate(origin.getExpireDate());
r.setSource(origin.getSource());
r.setRemark("__RATE_PATH__" + path);
return r;
}
/** 构造合成汇率(同币种/交叉换算用,无主表ID) */
private BmsExchangeRate buildSyntheticRate(String from, String to, String type, BigDecimal rate,
Date date, String path)
{
BmsExchangeRate r = new BmsExchangeRate();
r.setFromCurrency(from);
r.setToCurrency(to);
r.setRateType(type);
r.setExchangeRate(rate);
r.setEffectiveDate(date);
r.setSource(path.equals("SAME") ? "系统默认" : "交叉换算");
r.setRemark("__RATE_PATH__" + path);
return r;
}
private void normalize(BmsExchangeRate rate)
{
rate.setFromCurrency(normalizeCurrency(rate.getFromCurrency()));
rate.setToCurrency(normalizeCurrency(rate.getToCurrency()));
if (StringUtils.isEmpty(rate.getRateType()))
{
rate.setRateType(BmsExchangeRate.RATE_TYPE_SPOT);
}
if (StringUtils.isEmpty(rate.getSource()))
{
rate.setSource("手工录入");
}
}
private void validate(BmsExchangeRate rate)
{
if (StringUtils.isEmpty(rate.getFromCurrency()) || StringUtils.isEmpty(rate.getToCurrency()))
{
throw new ServiceException("源币种和目标币种不能为空");
}
if (rate.getFromCurrency().equals(rate.getToCurrency()))
{
throw new ServiceException("源币种与目标币种不能相同");
}
if (rate.getExchangeRate() == null || !positive(rate.getExchangeRate()))
{
throw new ServiceException("汇率值必须大于0");
}
if (rate.getEffectiveDate() == null)
{
throw new ServiceException("生效日期不能为空");
}
}
private void checkDuplicate(BmsExchangeRate rate)
{
int count = exchangeRateMapper.countDuplicate(rate.getFromCurrency(), rate.getToCurrency(),
rate.getRateType(), rate.getEffectiveDate(), rate.getRateId());
if (count > 0)
{
throw new ServiceException("已存在相同币种方向、类型、生效日期("
+ rate.getFromCurrency() + " → " + rate.getToCurrency() + " / "
+ rateTypeName(rate.getRateType()) + " / "
+ DateUtils.parseDateToStr("yyyy-MM-dd", rate.getEffectiveDate()) + ")的汇率配置");
}
}
private String normalizeCurrency(String currency)
{
if (StringUtils.isEmpty(currency))
{
throw new ServiceException("币种不能为空");
}
return currency.trim().toUpperCase();
}
private boolean positive(BigDecimal value)
{
return value != null && value.compareTo(BigDecimal.ZERO) > 0;
}
private String rateTypeName(String type)
{
switch (type)
{
case BmsExchangeRate.RATE_TYPE_MONTH_BEGIN: return "月初汇率";
case BmsExchangeRate.RATE_TYPE_MONTH_END: return "月末汇率";
case BmsExchangeRate.RATE_TYPE_FIXED: return "固定汇率";
case BmsExchangeRate.RATE_TYPE_SPOT:
default: return "即期汇率";
}
}
}
@@ -37,6 +37,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<!-- 账户余额/外币余额统一取 customer_account 余额表(CNY 为本币,其余为外币),不取 bms_customer 冗余列 -->
<!-- 信用额度=总额度+额外额度-已用额度-->
<sql id="selectBmsCustomerVo">
select c.customer_id, c.customer_code, c.customer_name, c.customer_code_dw, c.credit_code, c.customer_type,
c.is_enable, c.sales_user_id, c.sales_user, c.ed, c.zq, c.settlement_user_id, c.settlement_user,
@@ -45,7 +46,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where ca.customer_id = c.customer_id and ca.currency = 'CNY' and ca.del_flag = '0') as account_balance,
(select coalesce(sum(ca.balance), 0) from customer_account ca
where ca.customer_id = c.customer_id and ca.currency != 'CNY' and ca.del_flag = '0') as foreign_currency_balance,
c.weight_threshold, c.throw_ratio, c.credit_ed, c.product_ids, c.remark,
c.weight_threshold, c.throw_ratio,
(select coalesce(sum(ca.total_credit+ca.extra_credit-ca.used_credit), 0) from customer_account ca
where ca.customer_id = c.customer_id and ca.del_flag = '0') as credit_ed,
c.product_ids, c.remark,
c.create_by, c.create_time, c.update_by, c.update_time, c.del_flag
from bms_customer c
</sql>
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.BmsExchangeRateMapper">
<resultMap type="BmsExchangeRate" id="BmsExchangeRateResult">
<id property="rateId" column="rate_id" />
<result property="fromCurrency" column="from_currency" />
<result property="toCurrency" column="to_currency" />
<result property="rateType" column="rate_type" />
<result property="exchangeRate" column="exchange_rate" />
<result property="effectiveDate" column="effective_date" />
<result property="expireDate" column="expire_date" />
<result property="source" column="source" />
<result property="remark" column="remark" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="delFlag" column="del_flag" />
</resultMap>
<sql id="selectExchangeRateVo">
select rate_id, from_currency, to_currency, rate_type, exchange_rate, effective_date, expire_date,
source, remark, create_by, create_time, del_flag
from sys_exchange_rate
</sql>
<select id="selectExchangeRateList" parameterType="BmsExchangeRate" resultMap="BmsExchangeRateResult">
<include refid="selectExchangeRateVo"/>
<where>
del_flag = '0'
<if test="fromCurrency != null and fromCurrency != ''"> and from_currency = #{fromCurrency}</if>
<if test="toCurrency != null and toCurrency != ''"> and to_currency = #{toCurrency}</if>
<if test="rateType != null and rateType != ''"> and rate_type = #{rateType}</if>
<if test="source != null and source != ''"> and source like concat('%', #{source}, '%')</if>
<if test="params != null and params.beginTime != null and params.beginTime != ''">
and effective_date &gt;= #{params.beginTime}
</if>
<if test="params != null and params.endTime != null and params.endTime != ''">
and effective_date &lt;= #{params.endTime}
</if>
</where>
order by effective_date desc, rate_type asc, rate_id desc
</select>
<select id="selectExchangeRateById" parameterType="Long" resultMap="BmsExchangeRateResult">
<include refid="selectExchangeRateVo"/>
where rate_id = #{rateId} and del_flag = '0'
</select>
<select id="selectEffectiveRate" resultMap="BmsExchangeRateResult">
<include refid="selectExchangeRateVo"/>
where del_flag = '0'
and from_currency = #{fromCurrency}
and to_currency = #{toCurrency}
<if test="rateType != null and rateType != ''">and rate_type = #{rateType}</if>
and effective_date &lt;= #{bizDate}
and (expire_date is null or expire_date &gt;= #{bizDate})
order by effective_date desc, rate_id desc
limit 1
</select>
<select id="countDuplicate" resultType="int">
select count(1) from sys_exchange_rate
where del_flag = '0'
and from_currency = #{fromCurrency}
and to_currency = #{toCurrency}
and rate_type = #{rateType}
and effective_date = #{effectiveDate}
<if test="excludeId != null">and rate_id != #{excludeId}</if>
</select>
<insert id="insertExchangeRate" parameterType="BmsExchangeRate" useGeneratedKeys="true" keyProperty="rateId">
insert into sys_exchange_rate (
from_currency, to_currency, rate_type, exchange_rate, effective_date, expire_date,
source, remark, create_by, create_time, del_flag
) values (
#{fromCurrency}, #{toCurrency}, #{rateType}, #{exchangeRate}, #{effectiveDate}, #{expireDate},
#{source}, #{remark}, #{createBy}, sysdate(), '0'
)
</insert>
<update id="updateExchangeRate" parameterType="BmsExchangeRate">
update sys_exchange_rate
<set>
<if test="fromCurrency != null and fromCurrency != ''">from_currency = #{fromCurrency},</if>
<if test="toCurrency != null and toCurrency != ''">to_currency = #{toCurrency},</if>
<if test="rateType != null and rateType != ''">rate_type = #{rateType},</if>
<if test="exchangeRate != null">exchange_rate = #{exchangeRate},</if>
<if test="effectiveDate != null">effective_date = #{effectiveDate},</if>
expire_date = #{expireDate},
<if test="source != null">source = #{source},</if>
<if test="remark != null">remark = #{remark},</if>
</set>
where rate_id = #{rateId}
</update>
<update id="deleteExchangeRateByIds" parameterType="Long">
update sys_exchange_rate set del_flag = '1' where rate_id in
<foreach item="rateId" collection="array" open="(" separator="," close=")">
#{rateId}
</foreach>
</update>
</mapper>
+37
View File
@@ -0,0 +1,37 @@
-- ----------------------------
-- 汇率管理表 sys_exchange_rate
-- 汇率定义:1 单位源币种(from_currency) = exchange_rate 单位目标币种(to_currency)
-- 例:USD -> CNY, exchange_rate = 7.10000000 表示 1 USD = 7.1 CNY
-- ----------------------------
DROP TABLE IF EXISTS `sys_exchange_rate`;
CREATE TABLE `sys_exchange_rate` (
`rate_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '汇率ID',
`from_currency` varchar(10) NOT NULL COMMENT '源币种',
`to_currency` varchar(10) NOT NULL COMMENT '目标币种',
`rate_type` char(1) DEFAULT '0' COMMENT '汇率类型(0即期汇率 1月初汇率 2月末汇率 3固定汇率)',
`exchange_rate` decimal(18,8) NOT NULL COMMENT '汇率值(1源币种=?目标币种)',
`effective_date` date NOT NULL COMMENT '生效日期',
`expire_date` date DEFAULT NULL COMMENT '失效日期',
`source` varchar(50) DEFAULT '' COMMENT '汇率来源(中国银行/央行/手工录入)',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标记(0存在 1删除)',
PRIMARY KEY (`rate_id`),
KEY `idx_currency_date` (`from_currency`, `to_currency`, `effective_date`),
KEY `idx_rate_type` (`rate_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='汇率表';
-- ----------------------------
-- 初始常用汇率(即期,2026-09-14 生效,长期有效;请按实际行情维护)
-- ----------------------------
INSERT INTO `sys_exchange_rate`
(`from_currency`, `to_currency`, `rate_type`, `exchange_rate`, `effective_date`, `expire_date`, `source`, `remark`, `create_by`, `create_time`)
VALUES
('USD', 'CNY', '0', 7.10000000, '2026-09-14', NULL, '中国银行', '美元兑人民币即期汇率', 'admin', NOW()),
('EUR', 'CNY', '0', 7.85000000, '2026-09-14', NULL, '中国银行', '欧元兑人民币即期汇率', 'admin', NOW()),
('GBP', 'CNY', '0', 9.05000000, '2026-09-14', NULL, '中国银行', '英镑兑人民币即期汇率', 'admin', NOW()),
('HKD', 'CNY', '0', 0.91000000, '2026-09-14', NULL, '中国银行', '港币兑人民币即期汇率', 'admin', NOW()),
('JPY', 'CNY', '0', 0.04800000, '2026-09-14', NULL, '中国银行', '日元兑人民币即期汇率(1日元)', 'admin', NOW()),
('CNY', 'USD', '0', 0.14084507, '2026-09-14', NULL, '手工录入', '人民币兑美元(USD/CNY倒数)', 'admin', NOW()),
('CNY', 'EUR', '0', 0.12738854, '2026-09-14', NULL, '手工录入', '人民币兑欧元(EUR/CNY倒数)', 'admin', NOW());