feature: 确认书模板修改

This commit is contained in:
张名锐
2026-06-09 13:38:10 +08:00
parent a6e6980f9b
commit 4e9b2d7b47
6 changed files with 160 additions and 62 deletions
@@ -124,7 +124,7 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
protected override string GetOutputFileName(string contractNo, string contractIndex)
{
var trade = Context.Trade;
var rule = $"{trade.TradeType}交易确认书_{contractNo}";
var rule = $"【待用印】{trade.TradeType}交易确认书_{contractNo}";
return $"{rule}.{DocType.ToLower()}";
}
@@ -152,9 +152,9 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
dic["乙方名称"] = client.Name;
// 交易日期相关
dic["成交日期"] = trade.TradeDate?.ToString("yyyy年M月d日");
dic["开始日期"] = trade.StartDate?.ToString("yyyy年M月d日");
dic["到期日期"] = trade.ExerciseDate?.ToString("yyyy年M月d日");
dic["成交日期"] = trade.TradeDate?.ToString("yyyy】年【M】月【d】日");
dic["开始日期"] = trade.StartDate?.ToString("yyyy】年【M】月【d】日");
dic["到期日期"] = trade.ExerciseDate?.ToString("yyyy】年【M】月【d】日");
// 根据交易类型填充不同数据
switch (trade.TradeType)
@@ -222,8 +222,8 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
var bond = JsonHelper.Deserialize<UnderlyingBond>(underlying.ExJson) ?? new UnderlyingBond();
dic["参考标的发行人"] = bond.UnderlyingIssuer ?? "";
dic["参考标的担保人"] = "";
dic["票面利率"] = ((double)(bond.CouponRate ?? 0) * 100).ToString("N4");
dic["参考标的到期日"] = underlying.MaturityDate?.ToString("yyyy年M月d日") ?? "";
dic["票面利率"] = ((double)(bond.CouponRate ?? 0)).ToString("0.00");
dic["参考标的到期日"] = underlying.MaturityDate?.ToString("yyyy】年【M】月【d】日") ?? "";
}
// 从swap_position获取期初价格、保证金率等信息
@@ -235,7 +235,7 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
if (swapPosition != null)
{
// 期初全价和净价(转换为百分比格式)
dic["参考标的期初全价"] = ((double)swapPosition.PosiGrossPrice).ToString("N4");
dic["参考标的期初全价"] = ((double)swapPosition.PosiGrossPrice).ToString("0.000");
dic["参考标的期初净价"] = ((double)(swapPosition.PosiNetNoFeePrice ?? 0m)).ToString("N4");
dic["参考标的期初全价%"] = ((double)swapPosition.PosiGrossPrice * 100).ToString("N4");
@@ -257,40 +257,43 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
dic["维持保障金率"] = ((double)clientMarginRate.maintain_rate * 100).ToString("N4");
// 期初预付比例和金额
dic["期初预付比例"] = ((double)clientMarginRate.init_rate * 100).ToString("N4");
dic["期初预付比例"] = ((double)clientMarginRate.init_rate * 100).ToString("0.##");
dic["期初预付金额"] = ((trade.OriginalStockEqvNotional ?? 0) * (double)clientMarginRate.init_rate).ToString("N2");
}
else
{
dic["初始保障金率"] = "0.0000";
dic["维持保障金率"] = "0.0000";
dic["期初预付比例"] = "0.0000";
dic["期初预付比例"] = "0";
dic["期初预付金额"] = "0.00";
}
bool posiLong = IsCustomerLong(swapPosition);
// 计算平仓线、预警线、档位值(利率债TRS,固定4档)
var maintainRatePercent = (double)(clientMarginRate?.maintain_rate ?? 0) * 100; // 维持保证金率 A(%)
// A = 维持保证金率(%),B = 初始保证金率(%)diff = B - A
var maintainRatePercent = (double)(clientMarginRate?.maintain_rate ?? 0) * 100; // A(%)
var initRatePercent = (double)(clientMarginRate?.init_rate ?? 0) * 100; // B(%)
var diff = initRatePercent - maintainRatePercent; // B - A
if (maintainRatePercent > 0)
{
if (posiLong)
{
// 客户看多:档位n = 100 - n * A
dic["平仓线"] = (100 - 4 * maintainRatePercent).ToString("0.##");
dic["预警线"] = (100 - 3 * maintainRatePercent).ToString("0.##");
dic["档位1"] = (100 - 1 * maintainRatePercent).ToString("0.##");
dic["档位2"] = (100 - 2 * maintainRatePercent).ToString("0.##");
dic["档位3"] = (100 - 3 * maintainRatePercent).ToString("0.##");
dic["档位4"] = (100 - 4 * maintainRatePercent).ToString("0.##");
// 客户看多:档位n = 100 - n * (B-A)
dic["档位1"] = (100 - 1 * diff).ToString("0.##");
dic["档位2"] = (100 - 2 * diff).ToString("0.##");
dic["档位3"] = (100 - 3 * diff).ToString("0.##");
dic["档位4"] = (100 - 4 * diff).ToString("0.##");
dic["预警线"] = dic["档位4"]; // 预警线 = 最高档位
dic["平仓线"] = (100 - 5 * diff).ToString("0.##"); // 平仓线 = 最高档位 - (B-A)
}
else
{
// 客户看空:档位n = 100 + n * A
dic["平仓线"] = (100 + 4 * maintainRatePercent).ToString("0.##");
dic["预警线"] = (100 + 3 * maintainRatePercent).ToString("0.##");
dic["档位1"] = (100 + 1 * maintainRatePercent).ToString("0.##");
dic["档位2"] = (100 + 2 * maintainRatePercent).ToString("0.##");
dic["档位3"] = (100 + 3 * maintainRatePercent).ToString("0.##");
dic["档位4"] = (100 + 4 * maintainRatePercent).ToString("0.##");
// 客户看空:档位n = 100 + n * (B-A)
dic["档位1"] = (100 + 1 * diff).ToString("0.##");
dic["档位2"] = (100 + 2 * diff).ToString("0.##");
dic["档位3"] = (100 + 3 * diff).ToString("0.##");
dic["档位4"] = (100 + 4 * diff).ToString("0.##");
dic["预警线"] = dic["档位4"]; // 预警线 = 最高档位
dic["平仓线"] = (100 + 5 * diff).ToString("0.##"); // 平仓线 = 最高档位 + (B-A)
}
}
else
@@ -303,6 +306,96 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
dic["档位4"] = "";
}
// 追保事件/返还追保事件 价格区间和金额比例
if (maintainRatePercent > 0)
{
if (posiLong)
{
// === 客户看多 追保事件 ===
// 区间0:当前净价 ≥ {100-(B-A)}% × 期初净价,追保0%
dic["追保区间0下限"] = (100 - 1 * diff).ToString("0.##");
dic["追保金额比例0"] = "0";
// 区间1{100-2A}% ≤ 当前净价 < {100-A}%,追保{B-A}%
dic["追保区间1下限"] = (100 - 2 * maintainRatePercent).ToString("0.##");
dic["追保区间1上限"] = (100 - 1 * maintainRatePercent).ToString("0.##");
dic["追保金额比例1"] = (1 * diff).ToString("0.##");
// 区间2{100-3A}% ≤ 当前净价 < {100-2A}%,追保{2(B-A)}%
dic["追保区间2下限"] = (100 - 3 * maintainRatePercent).ToString("0.##");
dic["追保区间2上限"] = (100 - 2 * maintainRatePercent).ToString("0.##");
dic["追保金额比例2"] = (2 * diff).ToString("0.##");
// 区间3{100-4A}% ≤ 当前净价 < {100-3A}%,追保{3(B-A)}%
dic["追保区间3下限"] = (100 - 4 * maintainRatePercent).ToString("0.##");
dic["追保区间3上限"] = (100 - 3 * maintainRatePercent).ToString("0.##");
dic["追保金额比例3"] = (3 * diff).ToString("0.##");
// === 客户看多 返还追保事件 ===
// 区间0:当前净价 ≥ {100-(B-A)}% × 期初净价,返还0%
dic["返还区间0下限"] = (100 - 1 * diff).ToString("0.##");
dic["返还金额比例0"] = "0";
// 区间1{100-2(B-A)}% ≤ 当前净价 < {100-(B-A)}%,返还{B-A}%
dic["返还区间1下限"] = (100 - 2 * diff).ToString("0.##");
dic["返还区间1上限"] = (100 - 1 * diff).ToString("0.##");
dic["返还金额比例1"] = (1 * diff).ToString("0.##");
// 区间2{100-3(B-A)}% ≤ 当前净价 < {100-2(B-A)}%,返还{2(B-A)}%
dic["返还区间2下限"] = (100 - 3 * diff).ToString("0.##");
dic["返还区间2上限"] = (100 - 2 * diff).ToString("0.##");
dic["返还金额比例2"] = (2 * diff).ToString("0.##");
// 区间3{100-4(B-A)}% ≤ 当前净价 < {100-3(B-A)}%,返还{3(B-A)}%
dic["返还区间3下限"] = (100 - 4 * diff).ToString("0.##");
dic["返还区间3上限"] = (100 - 3 * diff).ToString("0.##");
dic["返还金额比例3"] = (3 * diff).ToString("0.##");
}
else
{
// === 客户看空 追保事件 ===
// 区间0:当前净价 ≤ {100+(B-A)}% × 期初净价,追保0%
dic["追保区间0上限"] = (100 + 1 * diff).ToString("0.##");
dic["追保金额比例0"] = "0";
// 区间1{100+(B-A)}% < 当前净价 ≤ {100+2(B-A)}%,追保{B-A}%
dic["追保区间1下限"] = (100 + 1 * diff).ToString("0.##");
dic["追保区间1上限"] = (100 + 2 * diff).ToString("0.##");
dic["追保金额比例1"] = (1 * diff).ToString("0.##");
// 区间2{100+2(B-A)}% < 当前净价 ≤ {100+3(B-A)}%,追保{2(B-A)}%
dic["追保区间2下限"] = (100 + 2 * diff).ToString("0.##");
dic["追保区间2上限"] = (100 + 3 * diff).ToString("0.##");
dic["追保金额比例2"] = (2 * diff).ToString("0.##");
// 区间3{100+3(B-A)}% < 当前净价 ≤ {100+4(B-A)}%,追保{3(B-A)}%
dic["追保区间3下限"] = (100 + 3 * diff).ToString("0.##");
dic["追保区间3上限"] = (100 + 4 * diff).ToString("0.##");
dic["追保金额比例3"] = (3 * diff).ToString("0.##");
// === 客户看空 返还追保事件 ===
// 区间0:当前净价 ≤ {100+(B-A)}% × 期初净价,返还0%
dic["返还区间0上限"] = (100 + 1 * diff).ToString("0.##");
dic["返还金额比例0"] = "0";
// 区间1{100+(B-A)}% < 当前净价 ≤ {100+2(B-A)}%,返还{B-A}%
dic["返还区间1下限"] = (100 + 1 * diff).ToString("0.##");
dic["返还区间1上限"] = (100 + 2 * diff).ToString("0.##");
dic["返还金额比例1"] = (1 * diff).ToString("0.##");
// 区间2{100+2(B-A)}% < 当前净价 ≤ {100+3(B-A)}%,返还{2(B-A)}%
dic["返还区间2下限"] = (100 + 2 * diff).ToString("0.##");
dic["返还区间2上限"] = (100 + 3 * diff).ToString("0.##");
dic["返还金额比例2"] = (2 * diff).ToString("0.##");
// 区间3{100+3(B-A)}% < 当前净价 ≤ {100+4(B-A)}%,返还{3(B-A)}%
dic["返还区间3下限"] = (100 + 3 * diff).ToString("0.##");
dic["返还区间3上限"] = (100 + 4 * diff).ToString("0.##");
dic["返还金额比例3"] = (3 * diff).ToString("0.##");
}
}
else
{
// 清空追保/返还相关字典值
for (int i = 0; i <= 3; i++)
{
dic[$"追保区间{i}下限"] = "";
dic[$"追保区间{i}上限"] = "";
dic[$"追保金额比例{i}"] = "";
dic[$"返还区间{i}下限"] = "";
dic[$"返还区间{i}上限"] = "";
dic[$"返还金额比例{i}"] = "";
}
}
// 期初预付金利率(InterestMode == 初始预付金)
var initialMarginPosition = swapPositions
.Where(x => x.InterestMode == (int)InterestModeEnum.)
@@ -326,8 +419,8 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
dic["IsFixed"] = "□";
dic["IsFloat"] = "□";
dic["期初观察日"] = trade.StartDate?.ToString("yyyy年M月d日") ?? "";
dic["期末观察日"] = trade.ExerciseDate?.ToString("yyyy年M月d日") ?? "";
dic["期初观察日"] = trade.StartDate?.ToString("yyyy】年【M】月【d】日") ?? "";
dic["期末观察日"] = trade.ExerciseDate?.ToString("yyyy】年【M】月【d】日") ?? "";
if (interestMargin != null)
{
if (string.IsNullOrEmpty(interestMargin.FloatRateUnderlyingCode))
@@ -349,8 +442,8 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
{
var firstInterval = interestMargin.SwapIntervalList.OrderBy(o=>o.Date).First();
var lastInterval = interestMargin.SwapIntervalList.OrderBy(o => o.Date).Last();
dic["期初观察日"] = firstInterval.Date.ToString("yyyy年M月d日") ?? "";
dic["期末观察日"] = lastInterval.Date.ToString("yyyy年M月d日") ?? "";
dic["期初观察日"] = firstInterval.Date.ToString("yyyy】年【M】月【d】日") ?? "";
dic["期末观察日"] = lastInterval.Date.ToString("yyyy】年【M】月【d】日") ?? "";
}
}
else
@@ -364,7 +457,7 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
var notional = trade.OriginalStockEqvNotional ?? 0;
var tradingFee = (double)swapPosition.PosiTradingFeePending;
var basicFeeRate = notional == 0 ? 0 : tradingFee / notional * 100;
dic["基本费率"] = basicFeeRate.ToString("N4");
dic["基本费率"] = basicFeeRate.ToString("0.##");
// 期初现金交换比例和金额(使用初始预付金数据)
dic["期初现金交换比例"] = initialMarginPosition != null
@@ -376,31 +469,31 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
}
else
{
dic["参考标的期初全价"] = "0.0000";
dic["参考标的期初全价"] = "0.000";
dic["参考标的期初净价"] = "0.0000";
dic["初始保障金率"] = "0.0000";
dic["维持保障金率"] = "0.0000";
dic["期初预付比例"] = "0.0000";
dic["期初预付比例"] = "0";
dic["期初预付金额"] = "0.00";
dic["期初预付金利率"] = "0.0000";
dic["利率类型"] = "固定利率";
dic["固定利率"] = "0.0000";
dic["利差"] = "";
dic["基本费率"] = "0.0000";
dic["基本费率"] = "0";
dic["期初现金交换比例"] = "0.0000";
dic["期初现金交换金额"] = "0.00";
}
// 参考标的券面总额(名义本金)
dic["参考标的券面总额"] = trade.OriginalStockEqvNotional?.ToString("N2") ?? "0.00";
dic["参考标的券面总额"] = swapPosition != null ? ((double)swapPosition.PosiQuantity).ToString("N2") : "0";
// 参考标的证券全称和参考标的名义份额(复用上方已声明的bond)
dic["参考标的证券全称"] = underlying != null
? (JsonHelper.Deserialize<UnderlyingBond>(underlying.ExJson)?.UnderlyingFullName ?? underlying.UnderlyingName)
: "";
dic["参考标的名义份额"] = swapPosition != null
? ((double)swapPosition.PosiQuantity).ToString("N2")
: "0.00";
? ((double)swapPosition.PosiQuantity).ToString("0.##")
: "0";
dic["参考标的基金管理人"] = "";
var contractTypeId = (Context.GetContractTypes().FirstOrDefault(O => O.ContactType == "交易确认书接收")?.id) ?? 0;
// 乙方联系人信息
@@ -417,8 +510,8 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
["标的代码"] = underlying?.UnderlyingCode ?? "",
["标的名称"] = underlying?.UnderlyingName ?? "",
["名义本金"] = trade.OriginalStockEqvNotional?.ToString("N2") ?? "0.00",
["成交日期"] = trade.TradeDate?.ToString("yyyy年M月d日"),
["到期日期"] = trade.ExerciseDate?.ToString("yyyy年M月d日")
["成交日期"] = trade.TradeDate?.ToString("yyyy】年【M】月【d】日"),
["到期日期"] = trade.ExerciseDate?.ToString("yyyy】年【M】月【d】日")
};
table1.Add(row);
dic["table1"] = table1;
@@ -8,8 +8,10 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
/// <summary>
/// 国贸交易确认书编号生成器
/// 规则:
/// - 对客交易(OpponentRole为"方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-{标的代码}-{当日第N笔对客交易(数字递增)},从001开始
/// - 非对客交易(OpponentRole为"乙方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-{标的代码}-{当日第N笔非对客交易(字母递增)},从A开始
/// - 对客交易(OpponentRole为"方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-{当日第N笔对客交易(数字递增)}-{标的代码},从01开始
/// 例:GLMS-IS-20260424-01-250210IB
/// - 非对客交易(OpponentRole为"甲方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-{当日第N笔非对客交易使用字母递增}-{标的代码},从A开始
/// 例:GLMS-IS-20260424-A-250210IB
/// </summary>
public class GuolianContractNoGenerator
{
@@ -27,12 +29,12 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
trade trade,
string clientCode)
{
// 根据trade的OpponentRole判断是否为对客交易
var isClientTrade = trade.OpponentRole == "方";
var underlyingCode = trade.UnderlyingCode ?? "";
// 根据trade的OpponentRole判断是否为对客交易(乙方=对客)
var isClientTrade = trade.OpponentRole == "方";
var underlyingCode = (trade.UnderlyingCode ?? "").Replace(".", ""); // 去掉标的代码中的点号
var tradeDate = trade.TradeDate ?? DateTime.MinValue;
var tradeDateStr = tradeDate.ToString("yyyyMMdd");
var prefix = $"GLMS-{clientCode}-{tradeDateStr}-{underlyingCode}-";
var prefix = $"GLMS-{clientCode}-{tradeDateStr}-";
lock (_syncLock)
{
@@ -63,62 +65,65 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
if (isClientTrade)
{
// 对客交易:GLMS-{clientCode}-{date}-{counter:D2}-{underlyingCode}
var maxNo = 0;
var prefixLen = prefix.Length;
for (var i = 0; i < existingCodes.Count; i++)
{
var code = existingCodes[i];
if (string.IsNullOrEmpty(code) || code.Length <= prefixLen)
if (string.IsNullOrEmpty(code) || code.Length <= prefixLen + 2)
{
continue;
}
var suffix = code.Substring(prefixLen);
if (int.TryParse(suffix, out var n) && n > maxNo)
var counterStr = code.Substring(prefixLen, 2);
if (int.TryParse(counterStr, out var n) && n > maxNo)
{
maxNo = n;
}
}
var nextNo = maxNo + 1;
var contractNo = $"{prefix}{nextNo:D3}";
var contractNo = $"{prefix}{nextNo:D2}-{underlyingCode}";
while (existingCodeSet.Contains(contractNo))
{
nextNo++;
contractNo = $"{prefix}{nextNo:D3}";
contractNo = $"{prefix}{nextNo:D2}-{underlyingCode}";
}
return contractNo;
}
else
{
var maxNo = 0;
// 非对客交易:GLMS-{clientCode}-{date}-{letter}-{underlyingCode}
// 找当日已有的非对客交易最大字母序号
var maxLetter = 0;
var prefixLen = prefix.Length;
for (var i = 0; i < existingCodes.Count; i++)
{
var code = existingCodes[i];
if (string.IsNullOrEmpty(code) || code.Length <= prefixLen)
if (string.IsNullOrEmpty(code) || code.Length <= prefixLen + 2)
{
continue;
}
var suffix = code.Substring(prefixLen);
if (!TryParseLettersToNumber(suffix, out var n))
var rest = code.Substring(prefixLen);
var dashIdx = rest.IndexOf('-');
if (dashIdx > 0)
{
continue;
}
if (n > maxNo)
{
maxNo = n;
var letterPart = rest.Substring(0, dashIdx);
if (TryParseLettersToNumber(letterPart, out var ln) && ln > maxLetter)
{
maxLetter = ln;
}
}
}
var nextNo = maxNo + 1;
var contractNo = $"{prefix}{NumberToLetter(nextNo)}";
var nextLetter = maxLetter + 1;
var contractNo = $"{prefix}{NumberToLetter(nextLetter)}-{underlyingCode}";
while (existingCodeSet.Contains(contractNo))
{
nextNo++;
contractNo = $"{prefix}{NumberToLetter(nextNo)}";
nextLetter++;
contractNo = $"{prefix}{NumberToLetter(nextLetter)}-{underlyingCode}";
}
return contractNo;
}