Files
zszq-trs/YLErpWeb/Controllers/BondPaymentController.cs
hjhan e3c473bafd fix(swap): 分红前部分平仓过的交易,后续平仓/互换分红收益改由 EOD 单一可信源提供
问题:GLMS-20260105-0004 在 2/28 部分平仓 40%、3/2 登记日互换后,3/3 继续平仓/互换时
"浮动端平仓盈亏·分红"误显 -36,160(应为 0)。根因:前端 getDivindIn 用期初全额持仓
×totalInterest 重算分红(-90,400),扣除 consumedDividend(-54,240) 后余 -36,160,
恰为已平仓 40%(登记日前已不持有、不享有该笔分红)部分被重复计入。

方案C:分红收益改由后端读上一收盘日 eod_swap_position.PosiDividendSum 提供(单一可信源),
前端不再自算。EOD 的 PosiDividendSum 已按"实际持仓递推 + 当日实现扣除"准确算出待实现分红,
3/2 互换后为 0。因手动平仓/互换后端不重算 DividendIn、直接落库前端值,后端 Init 阶段给出
正确值可同时修正展示与落库。

改动:
- SwapDealService 新增 GetPreEodDividendSum(复用 GetUnwindInterests:624 的上一 EOD 日期推导
  + SwapEodPositionService.GetPreEodPositions),protected virtual 便于测试覆写、无 EOD 返回 0
- InitUnwind/InitIncome 的 floatEvent.DividendIn 从硬编码 0 改为 GetPreEodDividendSum 返回值
- unwindSwapTrade.js/incomeSwapTrade.js getDivindIn 删除 GetBondPayMentInterest 调用与全部自算,
  保留后端 DividendIn 值、DividendPending=0
- GetBondPayMentInterest 加废弃文档注释(保留接口供历史调用,consumedDividend 无日期过滤隐患随废弃消解)

测试:GLMS20260105PartialCloseDividendBugTest 连真实库验收——3/3 平仓/互换 DividendIn=0(修复前
-36,160)、无 EOD 防御返回 0。全量构建 0 错误。
2026-08-06 19:09:45 +08:00

121 lines
4.7 KiB
C#

using Org.BouncyCastle.Ocsp;
using YLErp.DBModels;
using YLErp.Modules.EodModule;
namespace YLErp.Web.Controllers
{
public class BondPaymentController : BaseController
{
readonly IViewRenderService _viewRenderer;
public BondPaymentController(IViewRenderService viewRenderer)
{
_viewRenderer = viewRenderer;
}
[MyAuthorize("结算管理-债券付息数据查看")]
public ActionResult BondPaymentList()
{
return View();
}
[HttpPost]
public JsonResult BondPaymentQuery(BondPaymentReq req)
{
var sList = new BondPaymentService(CurUser).SearchList(req);
return Json(sList);
}
public ActionResult BondPaymentView(string enid)
{
var intid = DecryptLong(enid);
var r = yldb.bondPayment.Find(intid);
var um = yldb.underlying_manager.FirstOrDefault(x => x.UnderlyingCode == r.underlyingCode);
r.symbol = um?.UnderlyingName;
r.security_id = um?.UnderlyingCode;
return View(r);
}
public ActionResult BondPaymentEdit(string enid)
{
if (string.IsNullOrEmpty(enid) || enid == "0")
{
return View(new BondPayment());
}
var intid = DecryptLong(enid);
var dbmodel = yldb.bondPayment.Find(intid);
if (dbmodel == null)
{
return ShowError("找不到数据");
}
var um = yldb.underlying_manager.FirstOrDefault(x => x.UnderlyingCode == dbmodel.security_id);
dbmodel.symbol = um?.UnderlyingName;
return View(dbmodel);
}
public JsonResult BondPaymentEditJson(BondPayment req)
{
if (!string.IsNullOrEmpty(req.EncryptId))
{
req.id = DecryptLong(req.EncryptId);
}
var r = new BondPaymentService(CurUser).SaveBondPayment(req);
return JsonSuccess("更新成功", r);
}
[HttpPost]
public JsonResult DeletBondPayment(string id)
{
var intid = DecryptLong(id);
var r = yldb.bondPayment.Find(intid);
if (r == null)
{
return JsonError("找不到债券期间付息信息");
}
yldb.bondPayment.Remove(r);
yldb.SaveChanges();
return JsonSuccess("删除成功");
}
/// <summary>
/// 获取某债券期间付息,扣除当天已消费部分
/// <para>【已废弃·不再用于平仓/互换预览】分红展示改由后端 SwapDealService.GetPreEodDividendSum
/// 读 EOD PosiDividendSum 提供(方案C,单一可信源)。前端 unwindSwapTrade.js / incomeSwapTrade.js
/// 的 getDivindIn 不再调用本接口。本接口仅保留供历史调用方,consumedDividend 查询无日期过滤的
/// 隐患随废弃自然消解,不再单独修复。</para>
/// </summary>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <param name="underlyingCode"></param>
/// <param name="tradeId">交易ID(可选,用于扣除已消费分红)</param>
/// <param name="unwindDate">结算日期(可选)</param>
/// <returns></returns>
public JsonResult GetBondPayMentInterest(DateTime startDate, DateTime endDate, string underlyingCode, int? tradeId = null, DateTime? unwindDate = null)
{
var payments = new BondPaymentService(CurUser).GetBondPayments(underlyingCode, startDate, endDate);
decimal totalInterest = payments.Sum(s => s.payment_interest ?? 0) * 0.01m;
// 考虑增值税,与 EOD UpdateEodPosition 口径一致
var um = yldb.underlying_manager.FirstOrDefault(x => x.UnderlyingCode == underlyingCode);
if (um != null)
{
decimal tax = um.ValueAddedTax ?? 0;
totalInterest = totalInterest / (1 + tax) * (1 - tax);
}
decimal consumedDividend = 0;
if (tradeId.HasValue)
{
List<int> swapEventTypes = new List<int>() { (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换,(int)SwapEventTypeEnum.平仓 };
consumedDividend = yldb.swap_flow_event
.Where(x => x.SwapTradeId == tradeId.Value
&& swapEventTypes.Contains(x.EventType)
&& x.DataState == (int)SwapFlowDateStateEnum.完成)
.Sum(s => s.DividendIn);
}
return JsonSuccess("", new { totalInterest, consumedDividend });
}
}
}