Compare commits
13
Commits
b271a675d4
...
3d631e0a07
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d631e0a07 | ||
|
|
601eb2bed7 | ||
|
|
fdc0331407 | ||
|
|
ec11a1028a | ||
|
|
4c6251164d | ||
|
|
587c0d3fc2 | ||
|
|
fe38e59a9c | ||
|
|
a3e489fcd3 | ||
|
|
d2c0e9a9cb | ||
|
|
3a1f1958fc | ||
|
|
dc9ef5770b | ||
|
|
1f3fb9240b | ||
|
|
db95699aa2 |
@@ -559,9 +559,11 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
|
||||
}
|
||||
|
||||
// 基本费率 = PosiTradingFeePending / 名义本金 * 100
|
||||
var tradingFee = (double)swapPosition.PosiTradingFeePending;
|
||||
var basicFeeRate = notional == 0 ? 0 : tradingFee / notional * 100;
|
||||
dic["基本费率"] = basicFeeRate.ToString("0.####");
|
||||
// var tradingFee = (double)swapPosition.PosiTradingFeePending;
|
||||
// var basicFeeRate = notional == 0 ? 0 : tradingFee / notional * 100;
|
||||
// dic["基本费率"] = basicFeeRate.ToString("0.####");
|
||||
// @张名锐: 直接取值PosiTradingFeeUnit(基础费率)
|
||||
dic["基本费率"] = swapPosition.PosiTradingFeeUnit.ToString("0.####");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.Modules.MarginModule;
|
||||
|
||||
namespace YLErp.Modules.CalcModules
|
||||
{
|
||||
/// <summary>
|
||||
/// 模板同层多候选取舍纯函数测试(MarginTemplateV2RateHelper.PickByBookScopePreference,2026-08-28 裁定):
|
||||
/// 簿记条件满足者优先,其次生效日最新。不连库。
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class MarginTemplateV2BookScopePreferenceTest
|
||||
{
|
||||
private static readonly DateTime Newer = new DateTime(2026, 8, 1);
|
||||
private static readonly DateTime Older = new DateTime(2026, 1, 1);
|
||||
|
||||
private static margin_template_v2 Tpl(int id, DateTime valueDate, string bookIds = null)
|
||||
{
|
||||
return new margin_template_v2 { id = id, ValueDate = valueDate, BookIds = bookIds, IsValid = true };
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BP_01_双默认同日_限定簿记匹配_压过id更新的通配()
|
||||
{
|
||||
//复刻 dev 274/275 场景:同生效日按 id 降序,通配模板排前、限定簿记(45)排后;交易簿记=45
|
||||
var candidates = new List<margin_template_v2> { Tpl(275, Older), Tpl(274, Older, "45") };
|
||||
var picked = MarginTemplateV2RateHelper.PickByBookScopePreference(candidates, 45, out var byBook);
|
||||
Assert.AreEqual(274, picked.id, "簿记条件满足的限定模板应压过同日 id 更新的通配模板");
|
||||
Assert.IsTrue(byBook, "命中由簿记匹配优先产生时应置 preferredByBookScope");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BP_02_簿记不匹配_通配胜出()
|
||||
{
|
||||
//交易簿记=45 不在限定范围(38)内:限定模板不适用,按序取通配(2573 客需债券簿记场景)
|
||||
var candidates = new List<margin_template_v2> { Tpl(275, Older), Tpl(274, Older, "38") };
|
||||
var picked = MarginTemplateV2RateHelper.PickByBookScopePreference(candidates, 45, out var byBook);
|
||||
Assert.AreEqual(275, picked.id);
|
||||
Assert.IsFalse(byBook);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BP_03_无限定候选_取生效日最新()
|
||||
{
|
||||
var candidates = new List<margin_template_v2> { Tpl(2, Newer), Tpl(1, Older) };
|
||||
var picked = MarginTemplateV2RateHelper.PickByBookScopePreference(candidates, 45, out var byBook);
|
||||
Assert.AreEqual(2, picked.id, "无簿记限定候选时维持原口径:生效日最新胜出");
|
||||
Assert.IsFalse(byBook);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BP_04_多限定都匹配_取生效日最新()
|
||||
{
|
||||
var candidates = new List<margin_template_v2> { Tpl(2, Newer, "45,46"), Tpl(1, Older, "45") };
|
||||
var picked = MarginTemplateV2RateHelper.PickByBookScopePreference(candidates, 45, out var byBook);
|
||||
Assert.AreEqual(2, picked.id, "多个簿记条件满足的候选之间仍按生效日最新取第一个");
|
||||
Assert.IsFalse(byBook, "第一个适用候选即簿记匹配时不算'优先压过'");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BP_05_限定匹配_压过生效日更新的通配()
|
||||
{
|
||||
var candidates = new List<margin_template_v2> { Tpl(2, Newer), Tpl(1, Older, "45") };
|
||||
var picked = MarginTemplateV2RateHelper.PickByBookScopePreference(candidates, 45, out var byBook);
|
||||
Assert.AreEqual(1, picked.id, "簿记条件满足优先于生效日新旧");
|
||||
Assert.IsTrue(byBook);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BP_06_全部不适用_返回null()
|
||||
{
|
||||
var candidates = new List<margin_template_v2> { Tpl(1, Newer, "38") };
|
||||
var picked = MarginTemplateV2RateHelper.PickByBookScopePreference(candidates, 45, out _);
|
||||
Assert.IsNull(picked, "候选均不适用当前簿记账户时应返回 null(穿透/未命中由调用方处理)");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BP_07_交易无簿记账户_限定视为不适用()
|
||||
{
|
||||
//tradeId 查不到 AssetId 时入参为 null:显式限定模板视为不适用,通配仍可用
|
||||
var candidates = new List<margin_template_v2> { Tpl(2, Newer), Tpl(1, Older, "45") };
|
||||
var picked = MarginTemplateV2RateHelper.PickByBookScopePreference(candidates, null, out var byBook);
|
||||
Assert.AreEqual(2, picked.id);
|
||||
Assert.IsFalse(byBook);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BP_08_空候选或null_返回null()
|
||||
{
|
||||
Assert.IsNull(MarginTemplateV2RateHelper.PickByBookScopePreference(new List<margin_template_v2>(), 45, out _));
|
||||
Assert.IsNull(MarginTemplateV2RateHelper.PickByBookScopePreference(null, 45, out _));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ namespace YLErp.Modules.CalcModules
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
private margin_template_v2 AddTemplate(string name, bool isDefault, bool isForClient, string tradeTypes = "收益互换", bool isValid = true, int ruleType = (int)MarginRuleTypeEnum.区间追保结构)
|
||||
private margin_template_v2 AddTemplate(string name, bool isDefault, bool isForClient, string tradeTypes = "收益互换", bool isValid = true, int ruleType = (int)MarginRuleTypeEnum.区间追保结构, string bookIds = null, DateTime? valueDate = null)
|
||||
{
|
||||
var t = new margin_template_v2
|
||||
{
|
||||
@@ -65,7 +65,8 @@ namespace YLErp.Modules.CalcModules
|
||||
IsValid = isValid,
|
||||
TradeTypes = tradeTypes,
|
||||
RuleType = ruleType,
|
||||
ValueDate = EffectiveDate
|
||||
ValueDate = valueDate ?? EffectiveDate,
|
||||
BookIds = bookIds
|
||||
};
|
||||
db.margin_template_v2.Add(t);
|
||||
db.SaveChanges();
|
||||
@@ -179,6 +180,54 @@ namespace YLErp.Modules.CalcModules
|
||||
Assert.AreEqual(0.03m, rate.MaintainRate.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 双全局默认并存(2026-08-28 裁定回归,复刻 dev 274/275 结构):
|
||||
/// 同生效日下通配模板(无簿记限制)id 更新排前,限定簿记且匹配的模板排后——
|
||||
/// 原口径"先按生效日/id 取最新再验簿记"使通配恒胜、限定簿记模板永不生效;
|
||||
/// 新口径簿记条件满足者优先。需一笔隔离的真实交易(有簿记账户、无交易绑定,客户无客户级绑定)。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TF_009_双全局默认_限定簿记匹配优先于通配()
|
||||
{
|
||||
var boundTradeIds = db.trade_margin_template.AsNoTracking().Select(x => x.TradeId).ToList();
|
||||
var boundClientIds = db.client_margin_template.AsNoTracking()
|
||||
.Where(x => x.ClientId > 0).Select(x => x.ClientId).Distinct().ToList();
|
||||
var levelBoundNames = db.client_margin_template.AsNoTracking()
|
||||
.Where(x => x.ClientId == 0 && x.ClientLevel != null && x.ClientLevel != "")
|
||||
.Select(x => x.ClientLevel).Distinct().ToList();
|
||||
int tradeId, assetId, tradeClientId;
|
||||
using (var clientDb = DbContextFactory.GetClientDbContext(OptUserInfo.SystemUser))
|
||||
{
|
||||
var excludedLevelIds = clientDb.clientlevel.AsNoTracking()
|
||||
.Where(l => levelBoundNames.Contains(l.LevelName)).Select(l => l.id).ToList();
|
||||
var excludedClientIds = clientDb.client.AsNoTracking()
|
||||
.Where(c => boundClientIds.Contains(c.id) || (c.LevelId != null && excludedLevelIds.Contains(c.LevelId ?? 0)))
|
||||
.Select(c => c.id).ToList();
|
||||
var picked = db.trade.AsNoTracking()
|
||||
.Where(t => t.AssetId > 0 && t.ValidState != "InValid"
|
||||
&& !boundTradeIds.Contains(t.id) && !excludedClientIds.Contains(t.ClientId))
|
||||
.OrderByDescending(t => t.id)
|
||||
.Select(t => new { t.id, t.AssetId, t.ClientId }).FirstOrDefault();
|
||||
if (picked == null)
|
||||
{
|
||||
Assert.Inconclusive("dev 库无可用的隔离测试交易(有簿记账户且交易/客户均无绑定)");
|
||||
return;
|
||||
}
|
||||
tradeId = picked.id;
|
||||
assetId = picked.AssetId;
|
||||
tradeClientId = picked.ClientId;
|
||||
}
|
||||
|
||||
//同生效日(今日,晚于 dev 既有默认模板)双全局默认:通配后建(id 更大排前)
|
||||
var generic = AddTemplate(Marker + "全局通配", isDefault: true, isForClient: false, valueDate: DateTime.Today);
|
||||
var bookScoped = AddTemplate(Marker + "全局限定簿记", isDefault: true, isForClient: false, bookIds: assetId.ToString(), valueDate: DateTime.Today);
|
||||
|
||||
var resolved = MarginTemplateV2RateHelper.ResolveTieredTemplate(tradeId, tradeClientId, DateTime.Today, db);
|
||||
Assert.IsNotNull(resolved);
|
||||
Assert.IsTrue(resolved.Name.StartsWith(Marker), $"应命中本用例创建的标记模板,实际命中模板{resolved.id}(dev 出现同日/更晚生效默认模板会干扰,请重跑)");
|
||||
Assert.AreEqual(bookScoped.id, resolved.id, "限定簿记且匹配的全局默认应优先于同生效日的通配全局默认");
|
||||
}
|
||||
|
||||
private void AssertGlobal(margin_template_v2 resolved, margin_template_v2 expected)
|
||||
{
|
||||
Assert.IsNotNull(resolved);
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
using YLErp.Modules.EodModule.SettlementModule;
|
||||
|
||||
namespace YLErp.Modules.EodModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 收盘 FR007 定盘检查(EodCheckFr007Price)行为契约(纯内存,不连库):
|
||||
/// ① 检查日回拨:交易日 = 收盘日自身(对既有交易日收盘行为零变更);
|
||||
/// 周末/节假日 = 上一交易日——非交易日无 FR007 定盘,按裸收盘日检查会形成
|
||||
/// "收盘必报缺价"死锁(2026-08-28:0523 周六收盘即此症);
|
||||
/// ② 回拨规则与计息取价同源(IndexFixerBase.GetFixingDate),检查的日期
|
||||
/// 就是计息路径实际会读的日期;
|
||||
/// ③ 报错文案:交易日缺价保持旧格式,非交易日注明回拨,附交易编号供定位。
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class EodCheckFr007PriceTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void 检查日_交易日收盘_等于收盘日自身()
|
||||
{
|
||||
// 2026-05-22 周五、2026-08-28 周五:正常交易日收盘,行为必须与改造前一致
|
||||
Assert.AreEqual(new DateTime(2026, 5, 22), EodCheckFr007Price.GetCheckDate(new DateTime(2026, 5, 22)));
|
||||
Assert.AreEqual(new DateTime(2026, 8, 28), EodCheckFr007Price.GetCheckDate(new DateTime(2026, 8, 28)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void 检查日_周六收盘_回拨到上一交易日周五()
|
||||
{
|
||||
// 2026-08-28 缺陷场景:0523 为周六,FR007 非发布日永无定盘行,
|
||||
// 旧逻辑检查裸收盘日必报"未入库"且无法通过补交易要素绕过
|
||||
Assert.AreEqual(new DateTime(2026, 5, 22), EodCheckFr007Price.GetCheckDate(new DateTime(2026, 5, 23)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void 检查日_周日收盘_回拨跳过周末到周五()
|
||||
{
|
||||
// 与 GetInterestsUnitTest_T0 记录的取价回拨先例一致:GetNonHolidayDefore(4/26周日)→4/24周五
|
||||
Assert.AreEqual(new DateTime(2026, 4, 24), EodCheckFr007Price.GetCheckDate(new DateTime(2026, 4, 26)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void 检查日_与计息取价日同源()
|
||||
{
|
||||
// 同一收盘日,检查日必须等于计息路径 interest_rule=0 腿实际读的取价日
|
||||
var settleDate = new DateTime(2026, 5, 23);
|
||||
var interestPathFixingDate = YLErp.Derivatives.Interest.IndexFixerBase.GetFixingDate(settleDate, 0);
|
||||
Assert.AreEqual(interestPathFixingDate, EodCheckFr007Price.GetCheckDate(settleDate));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void 报错文案_交易日缺价_格式与旧版一致()
|
||||
{
|
||||
// 交易日缺价:不带回拨说明、无交易则不带交易段,保持"yyyy年MM月dd日FR007价格未入库"原格式
|
||||
var msg = EodCheckFr007Price.BuildMissingMessage(new DateTime(2026, 5, 22), new DateTime(2026, 5, 22), Array.Empty<string>());
|
||||
Assert.AreEqual("2026年05月22日FR007价格未入库", msg);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void 报错文案_非交易日回拨缺价_注明回拨()
|
||||
{
|
||||
var msg = EodCheckFr007Price.BuildMissingMessage(new DateTime(2026, 5, 23), new DateTime(2026, 5, 22), null);
|
||||
StringAssert.Contains(msg, "2026年05月22日FR007价格未入库");
|
||||
StringAssert.Contains(msg, "收盘日2026年05月23日为非交易日,已回拨至上一交易日检查");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void 报错文案_带交易编号供定位()
|
||||
{
|
||||
var msg = EodCheckFr007Price.BuildMissingMessage(
|
||||
new DateTime(2026, 5, 22), new DateTime(2026, 5, 22),
|
||||
new[] { "TRS-001", "TRS-002" });
|
||||
StringAssert.EndsWith(msg, ";涉及FR007浮动的交易: TRS-001、TRS-002");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void 报错文案_交易超过上限_截断并以等N笔收尾()
|
||||
{
|
||||
var trades = Enumerable.Range(1, 25).Select(i => $"TRS-{i:D3}").ToArray();
|
||||
var msg = EodCheckFr007Price.BuildMissingMessage(
|
||||
new DateTime(2026, 5, 22), new DateTime(2026, 5, 22), trades);
|
||||
StringAssert.Contains(msg, $"TRS-{EodCheckFr007Price.MaxTradesInMessage:D3}");
|
||||
StringAssert.Contains(msg, "等25笔");
|
||||
Assert.IsFalse(msg.Contains("TRS-021"), "超过上限的交易编号不应完整列出");
|
||||
Assert.IsFalse(msg.Contains("TRS-025、"), "截断后不应再拼接后续编号");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Dapper;
|
||||
using YLErp.Modules.UnderlyingModule;
|
||||
|
||||
namespace YLErp.UnitTestProject.Modules.UnderlyingModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 基金管理人取数(bigdata 数据源)连通性与库名插值验证(连 96 测试库):
|
||||
/// - DbSchema.Of 从连接串解析物理库名(glms_bigdata),SQL 不再硬编码库名、跨环境库名不同也能命中;
|
||||
/// - Lookup 全链路(连接 96 → 插值 SQL 执行 → 结果归并)不返回 Unavailable 即为连通且 SQL 有效。
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class FundManagerLookupServiceTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void DbSchema_从连接串解析物理库名()
|
||||
{
|
||||
Assert.AreEqual("`glms_bigdata`", DbSchema.Of("bigdata"));
|
||||
Assert.AreEqual("`glms_yltrs_ylcms`", DbSchema.Of("ylcms"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Lookup_连接96大数据库_非降级()
|
||||
{
|
||||
var service = new FundManagerLookupService();
|
||||
//96 库现有测试数据 161210/630006/511160(SECUCODE 无后缀,与去后缀匹配逻辑一致),用库存代码验证全链路命中
|
||||
foreach (var code in new[] { "511160.SH", "161210.SZ", "630006.SH" })
|
||||
{
|
||||
var result = service.Lookup(code);
|
||||
Console.WriteLine($"code={code} → Status={result.Status}, InvestAdvisorName={result.InvestAdvisorName}");
|
||||
Assert.AreNotEqual(FundManagerLookupStatus.Unavailable, result.Status,
|
||||
$"code={code} 返回 Unavailable:96 bigdata 库不可达或插值 SQL 执行失败");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Diag_查看库存secucode样例与行数()
|
||||
{
|
||||
using var connection = new MySqlConnector.MySqlConnection(AppManager.GetConnectionString("bigdata"));
|
||||
var count = connection.ExecuteScalar<long>($"SELECT COUNT(*) FROM {DbSchema.Of("bigdata")}.mf_fundarchives");
|
||||
var samples = connection.Query<string>($"SELECT SECUCODE FROM {DbSchema.Of("bigdata")}.mf_fundarchives LIMIT 8");
|
||||
Console.WriteLine($"mf_fundarchives 行数={count}, SECUCODE样例=[{string.Join(",", samples)}]");
|
||||
var advCount = connection.ExecuteScalar<long>($"SELECT COUNT(*) FROM {DbSchema.Of("bigdata")}.mf_investadvisoroutline");
|
||||
Console.WriteLine($"mf_investadvisoroutline 行数={advCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
"ylcms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"yladmin": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"ylclient": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"bondoms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=zszq_bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
|
||||
"bondoms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=zszq_bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"bigdata": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_bigdata;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
|
||||
},
|
||||
"LibreOffice": {
|
||||
"ExePath": "",
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using MySqlConnector;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace YLErp.BLL
|
||||
{
|
||||
/// <summary>
|
||||
/// 跨库 SQL 的物理库名解析器:代码里只写逻辑连接名(appsettings ConnectionStrings 的 key,
|
||||
/// 与 Java @Mapper 指定数据源名同语义),物理库名由各环境连接串的 database= 决定——
|
||||
/// 同一逻辑库在不同环境库名不同(如 bigdata 在测试环境为 glms_bigdata),SQL 中硬编码库名会跨环境失败。
|
||||
/// 用法:{@DbSchema.Of("bigdata")}.mf_fundarchives(返回带反引号的库名,可直接内插)。
|
||||
/// 仅标识自家的 appsettings 连接串,值不来自用户输入;仍做 \w+ 白名单校验防御配置笔误。
|
||||
/// </summary>
|
||||
public static class DbSchema
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, string> Cache = new();
|
||||
private static readonly Regex SafeIdentifier = new(@"^\w+$", RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// 取逻辑连接名对应的物理库名(形如 `glms_bigdata`,含反引号)。配置缺失或库名非法立即抛错——
|
||||
/// 跨库 SQL 拼错库名在运行期才暴露更难排查,配置错误应尽早失败。
|
||||
/// </summary>
|
||||
public static string Of(string connectionKey)
|
||||
{
|
||||
return Cache.GetOrAdd(connectionKey, key =>
|
||||
{
|
||||
var connectionString = AppManager.GetConnectionString(key);
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
throw new InvalidOperationException($"跨库SQL依赖的连接串未配置:{key}");
|
||||
}
|
||||
var database = new MySqlConnectionStringBuilder(connectionString).Database;
|
||||
if (string.IsNullOrWhiteSpace(database) || !SafeIdentifier.IsMatch(database))
|
||||
{
|
||||
throw new InvalidOperationException($"连接串 {key} 缺少 database 或库名非法:{database}");
|
||||
}
|
||||
return "`" + database + "`";
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using YLErp.Derivatives.Interest;
|
||||
using YLErp.Modules.DataProviderModule;
|
||||
|
||||
namespace YLErp.Modules.EodModule.SettlementModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 收盘 FR007 定盘入库检查(独立检查服务,由 EodCheckSettlePrice 在
|
||||
/// "检查标的结算价格缺失"步骤内编排调用——报错前缀沿用该步骤名,保持既有报错口径)。
|
||||
///
|
||||
/// 职责边界:只回答"计息会读的那个取价日,FR007 定盘行是否已入库"。
|
||||
/// 日期推导与计息路径共用同一套日历函数(IndexFixerBase.GetFixingDate),
|
||||
/// 保证检查口径 ≡ 取数口径。第二步完整版(按各浮动腿重置日推导所需日期集合 +
|
||||
/// 批量校验 + Fr007FixingCache 复用)也落在本类,不回填 EodCheckSettlePrice。
|
||||
/// </summary>
|
||||
public class EodCheckFr007Price : EodSettleServiceBaseV2
|
||||
{
|
||||
/// <summary>报错里最多列出的交易编号数,超出以"等N笔"收尾,防刷屏。</summary>
|
||||
internal const int MaxTradesInMessage = 20;
|
||||
|
||||
public EodCheckFr007Price(EodSettlementContextV2 context) : base(context)
|
||||
{
|
||||
ResetDbContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查结算日(回拨后的取价日)FR007 定盘是否入库;缺价抛 EodSettleException 终止收盘。
|
||||
/// step 由调用方传入:本检查隶属于"检查标的结算价格缺失"步骤,报错前缀与其保持一致。
|
||||
/// </summary>
|
||||
public void Execute(string step)
|
||||
{
|
||||
var checkDate = GetCheckDate(_context.SettleDate);
|
||||
if (!EodPriceQueryService.CheckFR007Price(checkDate))
|
||||
{
|
||||
var tradeNumbers = GetFloatTradeNumbers(); // 仅缺价时才查,正常收盘零开销
|
||||
_context.RaiseError(step, BuildMissingMessage(_context.SettleDate, checkDate, tradeNumbers));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 收盘 FR007 检查日:与计息取价共用同一套日历函数(IndexFixerBase.GetFixingDate),
|
||||
/// 保证"检查的日期"与"计息实际读的日期"始终一致。
|
||||
///
|
||||
/// 回拨发生在日历层(GetNonHolidayDefore),与 interest_rule 取值无关:
|
||||
/// 交易日 → 当日自身(工作日收盘行为零变更);
|
||||
/// 周末/节假日 → 上一交易日(非交易日无定盘,按裸收盘日检查会形成
|
||||
/// "非交易日收盘必报缺价"的死锁——2026-08-28 缺陷:0523 周六收盘
|
||||
/// 报"0523 FR007未入库",且检查只看收盘日不看任何交易,改交易要素无法绕过)。
|
||||
/// 抽成纯函数便于无数据库单测。
|
||||
/// </summary>
|
||||
internal static DateTime GetCheckDate(DateTime settleDate)
|
||||
=> IndexFixerBase.GetFixingDate(settleDate.Date, 0);
|
||||
|
||||
/// <summary>
|
||||
/// 组装 FR007 缺价报错文案(纯函数便于单测):
|
||||
/// 显示实际检查日(= 计息会读的取价日);收盘日为非交易日时注明已回拨;
|
||||
/// 附涉及 FR007 浮动的交易编号供定位——注意这是"涉及"而非"精确需要该日定盘"
|
||||
/// (精确归因需按各腿重置日推导,属第二步完整版改造,此处宁多报不漏报)。
|
||||
/// </summary>
|
||||
internal static string BuildMissingMessage(DateTime settleDate, DateTime checkDate, IReadOnlyList<string> fr007TradeNumbers)
|
||||
{
|
||||
var rollbackNote = checkDate != settleDate.Date
|
||||
? $"(收盘日{settleDate:yyyy年MM月dd日}为非交易日,已回拨至上一交易日检查)"
|
||||
: string.Empty;
|
||||
|
||||
string tradeNote;
|
||||
if (fr007TradeNumbers == null || fr007TradeNumbers.Count == 0)
|
||||
{
|
||||
tradeNote = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
var listed = string.Join("、", fr007TradeNumbers.Take(MaxTradesInMessage));
|
||||
tradeNote = fr007TradeNumbers.Count > MaxTradesInMessage
|
||||
? $";涉及FR007浮动的交易: {listed} 等{fr007TradeNumbers.Count}笔"
|
||||
: $";涉及FR007浮动的交易: {listed}";
|
||||
}
|
||||
|
||||
return $"{checkDate:yyyy年MM月dd日}FR007价格未入库{rollbackNote}{tradeNote}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结算范围内挂 FR007 浮动利率的交易编号。仅在缺价报错时调用(正常收盘零开销)。
|
||||
/// 口径是"结算范围内 FR007 浮动腿所属交易",不含重置日归因——非重置日的交易当日
|
||||
/// 不取价、也一并列出,定位时宁多报不漏报。
|
||||
/// </summary>
|
||||
private string[] GetFloatTradeNumbers()
|
||||
{
|
||||
var tradePredicate = _context.PredicateBuilder.GetOtcSwapTradePredicate();
|
||||
return (from t in DbContext.trade.Where(tradePredicate)
|
||||
join p in DbContext.swap_position on t.id equals p.SwapTradeId
|
||||
where !p.Invalid && p.FloatRateUnderlyingCode == "FR007"
|
||||
select t.TradeNumber)
|
||||
.Distinct()
|
||||
.OrderBy(n => n)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,10 +32,9 @@ namespace YLErp.Modules.EodModule.SettlementModule
|
||||
var clienIds = _context.Request.ClientIds;
|
||||
|
||||
IQueryable<string> allQuery = null;
|
||||
if (!EodPriceQueryService.CheckFR007Price(_context.SettleDate))
|
||||
{
|
||||
_context.RaiseError(Step, $"{_context.SettleDate:yyyy年MM月dd日}FR007价格未入库");
|
||||
}
|
||||
// FR007 定盘检查委托给独立检查服务(职责分离):检查日=计息实际读取的取价日,
|
||||
// 交易日=当日、周末/节假日=上一交易日,报错沿用本步骤名保持既有口径。
|
||||
new EodCheckFr007Price(_context).Execute(Step);
|
||||
//判断当日结算价是否已经入库
|
||||
if (!EodPriceQueryService.CheckDbExists(_context.SettleDate))
|
||||
{
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace YLErp.Modules.MarginModule
|
||||
/// <summary>
|
||||
/// 预付金模板V2 取数帮助类:按交易绑定的模板(trade_margin_template → margin_template_v2 → margin_template_detail)
|
||||
/// 取 初始预付金率x / 维持预付金率y,供交易确认书、预付金计算等消费点统一使用。
|
||||
/// 匹配维度:簿记账户范围(BookIds,空=全部)→ 生效日期(ValueDate 最新)→
|
||||
/// 利率债期限档(SpanConfig.BondTerm,四档,空=全部兜底)→ 标的资产类型(UnderlyingType 标志位)。
|
||||
/// 匹配维度:簿记账户范围(BookIds 显式限定且匹配者优先于空=全部的通配,同层多候选 2026-08-28 裁定)→
|
||||
/// 生效日期(ValueDate 最新)→ 利率债期限档(SpanConfig.BondTerm,四档,空=全部兜底)→ 标的资产类型(UnderlyingType 标志位)。
|
||||
/// </summary>
|
||||
public static class MarginTemplateV2RateHelper
|
||||
{
|
||||
@@ -214,6 +214,7 @@ namespace YLErp.Modules.MarginModule
|
||||
/// 客户等级经 client.LevelId → clientlevel.LevelName 解析;ValueDate 最新 ≤ 业务日),
|
||||
/// 模板须有效、适用结构含收益互换且适用当前交易簿记账户;
|
||||
/// 3.全局默认 margin_template_v2(IsDefault && !IsForClient && IsValid && 适用收益互换,且适用当前簿记账户,ValueDate 最新)。
|
||||
/// 2/3 层多候选并存时按 PickByBookScopePreference 取舍:簿记条件满足者优先,其次生效日最新(2026-08-28)。
|
||||
/// </summary>
|
||||
public static margin_template_v2 ResolveTieredTemplate(int? tradeId, int clientId, DateTime valueDate, YLContext db)
|
||||
{
|
||||
@@ -269,24 +270,24 @@ namespace YLErp.Modules.MarginModule
|
||||
orderby cmt.ValueDate descending, cmt.id descending
|
||||
select mt).ToList();
|
||||
|
||||
var ret = clientTemplate.FirstOrDefault(x => x.IsApplicableToBook(tradeAssetId));
|
||||
//同层多候选:簿记条件满足者优先,其次绑定生效日最新(2026-08-28 裁定)
|
||||
var ret = PickByBookScopePreference(clientTemplate, tradeAssetId, out var clientByBook);
|
||||
if (ret != null)
|
||||
{
|
||||
logger.Info($"预付金模板取数:交易{tradeId}(客户{clientId},等级{levelName ?? "无"})命中层级=客户默认 → 模板{ret.id}");
|
||||
logger.Info($"预付金模板取数:交易{tradeId}(客户{clientId},等级{levelName ?? "无"})命中层级=客户默认 → 模板{ret.id}{(clientByBook ? "(簿记账户匹配优先)" : "")}");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
//3.全局默认
|
||||
var globalDefault = db.margin_template_v2.AsNoTracking()
|
||||
//3.全局默认(同层多候选:簿记条件满足者优先,其次模板生效日最新——双全局默认并存时通配模板不再凭 id 最新恒胜,2026-08-28)
|
||||
var globalDefault = PickByBookScopePreference(db.margin_template_v2.AsNoTracking()
|
||||
.Where(x => x.IsDefault && !x.IsForClient && x.IsValid && x.TradeTypes.Contains("收益互换") && x.ValueDate <= valueDate)
|
||||
.OrderByDescending(x => x.ValueDate)
|
||||
.ThenByDescending(x => x.id)
|
||||
.ToList()
|
||||
.FirstOrDefault(x => x.IsApplicableToBook(tradeAssetId));
|
||||
.ToList(), tradeAssetId, out var globalByBook);
|
||||
if (globalDefault != null)
|
||||
{
|
||||
logger.Info($"预付金模板取数:交易{tradeId}(客户{clientId})命中层级=全局默认 → 模板{globalDefault.id}");
|
||||
logger.Info($"预付金模板取数:交易{tradeId}(客户{clientId})命中层级=全局默认 → 模板{globalDefault.id}{(globalByBook ? "(簿记账户匹配优先)" : "")}");
|
||||
}
|
||||
return globalDefault;
|
||||
}
|
||||
@@ -298,6 +299,7 @@ namespace YLErp.Modules.MarginModule
|
||||
/// 1.交易绑定(ValueDate 最新 ≤ 业务日,同日多条按 id 最新兜底)找到即停,绑定指向失效模板同样停止(不回退);
|
||||
/// 2.客户默认(client_margin_template 按客户或按客户等级,模板须有效、适用结构含收益互换且适用当前簿记账户,先过滤有效再取绑定最新);
|
||||
/// 3.全局默认(IsDefault&&!IsForClient&&IsValid&&适用收益互换,按每笔交易簿记账户取ValueDate/id 最新)。
|
||||
/// 2/3 层多候选并存时按 PickByBookScopePreference 取舍:簿记条件满足者优先,其次生效日最新(2026-08-28)。
|
||||
/// 返回 tradeId → 命中模板;未命中或因失效绑定停止的交易不在结果中。
|
||||
/// </summary>
|
||||
public static Dictionary<int, margin_template_v2> ResolveTieredTemplates(List<trade> trades, DateTime valueDate, YLContext db)
|
||||
@@ -400,20 +402,23 @@ namespace YLErp.Modules.MarginModule
|
||||
continue;
|
||||
}
|
||||
var levelName = levelNames.TryGetValue(trade.ClientId, out var clientLevelName) ? clientLevelName : null;
|
||||
var clientTemplate = clientTemplateCandidates.FirstOrDefault(x =>
|
||||
(x.ClientId == trade.ClientId || (x.ClientId == 0 && x.ClientLevel == levelName))
|
||||
&& x.Template.IsApplicableToBook(trade.AssetId));
|
||||
//同层多候选:先按客户/等级筛出候选(保持绑定生效日降序),再簿记条件满足者优先、其次生效日最新(与单笔解析一致)
|
||||
var clientMatchCandidates = clientTemplateCandidates
|
||||
.Where(x => x.ClientId == trade.ClientId || (x.ClientId == 0 && x.ClientLevel == levelName))
|
||||
.Select(x => x.Template)
|
||||
.ToList();
|
||||
var clientTemplate = PickByBookScopePreference(clientMatchCandidates, trade.AssetId, out var clientByBook);
|
||||
if (clientTemplate != null)
|
||||
{
|
||||
result[trade.id] = clientTemplate.Template;
|
||||
logger.Info($"预付金模板取数:交易{trade.id}(客户{trade.ClientId},等级{levelName ?? "无"})命中层级=客户默认 → 模板{clientTemplate.Template.id}");
|
||||
result[trade.id] = clientTemplate;
|
||||
logger.Info($"预付金模板取数:交易{trade.id}(客户{trade.ClientId},等级{levelName ?? "无"})命中层级=客户默认 → 模板{clientTemplate.id}{(clientByBook ? "(簿记账户匹配优先)" : "")}");
|
||||
continue;
|
||||
}
|
||||
var globalDefault = globalDefaults.FirstOrDefault(x => x.IsApplicableToBook(trade.AssetId));
|
||||
var globalDefault = PickByBookScopePreference(globalDefaults, trade.AssetId, out var globalByBook);
|
||||
if (globalDefault != null)
|
||||
{
|
||||
result[trade.id] = globalDefault;
|
||||
logger.Info($"预付金模板取数:交易{trade.id}(客户{trade.ClientId})命中层级=全局默认 → 模板{globalDefault.id}");
|
||||
logger.Info($"预付金模板取数:交易{trade.id}(客户{trade.ClientId})命中层级=全局默认 → 模板{globalDefault.id}{(globalByBook ? "(簿记账户匹配优先)" : "")}");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -424,6 +429,35 @@ namespace YLErp.Modules.MarginModule
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同层级多候选并存时的取舍(2026-08-28 裁定,纯函数单测覆盖):
|
||||
/// 入参候选序须已按生效日最新在前(客户层=绑定生效日,全局层=模板生效日,同日按 id 最新)。
|
||||
/// 1.存在"簿记条件满足"的候选(BookIds 显式限定且经 IsApplicableToBook 命中当前交易簿记账户)→ 优先取之,
|
||||
/// 多个命中时仍按传入顺序取生效日最新;
|
||||
/// 2.否则取顺序第一个(生效日最新)。
|
||||
/// 背景:双全局默认并存(如 无预付金限定簿记 + 区间追保通配)时,原"先按生效日/id 取最新再验簿记"
|
||||
/// 使通配模板恒胜、限定簿记模板永不生效(交易2572/2573 实证)。
|
||||
/// preferredByBookScope=true 表示本次由簿记匹配压过了顺序更前的通配候选(供日志辨识)。
|
||||
/// </summary>
|
||||
public static margin_template_v2 PickByBookScopePreference(List<margin_template_v2> candidatesByLatestFirst, int? tradeAssetId, out bool preferredByBookScope)
|
||||
{
|
||||
preferredByBookScope = false;
|
||||
if (candidatesByLatestFirst == null || candidatesByLatestFirst.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var applicable = candidatesByLatestFirst.Where(x => x.IsApplicableToBook(tradeAssetId)).ToList();
|
||||
var bookMatched = applicable.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.BookIds));
|
||||
if (bookMatched == null)
|
||||
{
|
||||
return applicable.FirstOrDefault();
|
||||
}
|
||||
|
||||
preferredByBookScope = applicable[0].id != bookMatched.id;
|
||||
return bookMatched;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标的细分分类判定钩子(ETF 子类,2026-08-24 实装):基金类标的返回标的维护页维护的
|
||||
/// underlying_manager.EtfSubType(取值来自数据字典"ETF 子类":国债 ETF/政金债 ETF/…/科创债 ETF/可转债 ETF,
|
||||
|
||||
@@ -3,6 +3,7 @@ using Newtonsoft.Json.Linq;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.Helpers;
|
||||
using YLErp.Modules.ApiModule;
|
||||
using YLErp.Modules.TradeModule.DealModule;
|
||||
|
||||
@@ -225,13 +226,13 @@ namespace YLErp.Modules.TradeModule
|
||||
&& (string.IsNullOrWhiteSpace(requestId) || x.oa_fileid == requestId)
|
||||
&& (x.status == Pending || (x.status == Syncing && (x.last_query_time == null || x.last_query_time < staleAt))))
|
||||
.ToList();
|
||||
_logger.Info($"开始轮询 OA 移动审批状态,requestId:{requestId ?? "全部"},待查询数量:{pending.Count}");
|
||||
_logger.Info($"开始轮询 OA 移动审批状态,requestId:{requestId ?? "全部"},待查询数量:{pending.Count},同步中超时阈值:{staleAt:yyyy-MM-dd HH:mm:ss.fff}");
|
||||
foreach (var record in pending)
|
||||
{
|
||||
var claimed = false;
|
||||
try
|
||||
{
|
||||
_logger.Info($"查询 OA 移动审批状态,交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid}");
|
||||
_logger.Info($"查询 OA 移动审批状态,OA记录:{record.id},交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},本地状态:{record.status},上次查询:{record.last_query_time:yyyy-MM-dd HH:mm:ss.fff}");
|
||||
var response = Query(record.oa_fileid);
|
||||
record.last_response = response.ToString();
|
||||
var flow = response["data"] is JArray data
|
||||
@@ -251,31 +252,37 @@ namespace YLErp.Modules.TradeModule
|
||||
var isReturned = flowStatusType == "0" && flowNode == "退回";
|
||||
var isApproved = flowStatusType == "3"
|
||||
&& (flowNode == "结束" || flowNode == "强制归档");
|
||||
_logger.Info($"OA 移动审批查询结果,交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},flowStatusType:{flowStatusType},flowNode:{flowNode},判定:{(isReturned ? "退回" : isApproved ? "通过" : "等待")}");
|
||||
_logger.Info($"OA 移动审批查询结果,OA记录:{record.id},交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},是否命中流程:{flow != null},flowStatusType:{flowStatusType},flowNode:{flowNode},是否退回:{isReturned},是否通过:{isApproved},判定:{(isReturned ? "退回" : isApproved ? "通过" : "等待")}");
|
||||
if (!isApproved && !isReturned)
|
||||
{
|
||||
_logger.Info($"OA 移动审批继续等待,更新最近查询时间但不修改本地审批流程,OA记录:{record.id},requestId:{record.oa_fileid},本地状态保持:{record.status}");
|
||||
record.last_query_time = DateTime.Now;
|
||||
DbContext.SaveChanges();
|
||||
continue;
|
||||
}
|
||||
|
||||
DbContext.SaveChanges();
|
||||
if (!TryClaimForSync(record.id))
|
||||
_logger.Info($"OA 审批结果满足回写条件,准备将记录设为同步中,OA记录:{record.id},交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},当前本地状态:{record.status}");
|
||||
var claimAffectedRows = ClaimForSync(record.id);
|
||||
if (claimAffectedRows != 1)
|
||||
{
|
||||
_logger.Info($"OA 审批结果同步未抢占记录,交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid}");
|
||||
_logger.Info($"OA 审批结果未能将记录设为同步中,OA记录:{record.id},requestId:{record.oa_fileid},更新行数:{claimAffectedRows}");
|
||||
continue;
|
||||
}
|
||||
claimed = true;
|
||||
_logger.Info($"OA 审批结果已将记录设为同步中,OA记录:{record.id},requestId:{record.oa_fileid},更新行数:{claimAffectedRows}");
|
||||
|
||||
var trade = DbContext.trade.Find(record.trade_id);
|
||||
if (trade == null)
|
||||
{
|
||||
SetClaimedStatus(record.id, "同步失败", "交易不存在");
|
||||
var statusAffectedRows = SetClaimedStatus(record.id, "同步失败", "交易不存在");
|
||||
_logger.Error($"OA 审批结果不再回写,交易不存在,OA记录:{record.id},requestId:{record.oa_fileid},目标状态:同步失败,更新行数:{statusAffectedRows}");
|
||||
continue;
|
||||
}
|
||||
if (GetCurrentNode(trade)?.id != record.approval_process_id)
|
||||
{
|
||||
SetClaimedStatus(record.id, "同步忽略", "当前审批节点已变化,忽略 OA 结果");
|
||||
var statusAffectedRows = SetClaimedStatus(record.id, "同步忽略", "当前审批节点已变化,忽略 OA 结果");
|
||||
_logger.Info($"OA 审批结果不再回写,当前审批节点已变化,OA记录:{record.id},requestId:{record.oa_fileid},目标状态:同步忽略,更新行数:{statusAffectedRows}");
|
||||
continue;
|
||||
}
|
||||
var result = new TradeOpenService(this).UpdateTradeProcessLog(new TradeOpenReqModel
|
||||
@@ -288,13 +295,14 @@ namespace YLErp.Modules.TradeModule
|
||||
});
|
||||
if (!string.IsNullOrWhiteSpace(result.ErrorMsg))
|
||||
{
|
||||
SetClaimedStatus(record.id, Pending, result.ErrorMsg);
|
||||
_logger.Error($"OA 审批结果回写本地失败,交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},原因:{result.ErrorMsg}");
|
||||
var statusAffectedRows = SetClaimedStatus(record.id, Pending, result.ErrorMsg);
|
||||
_logger.Error($"OA 审批结果回写本地失败,OA记录:{record.id},交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},目标状态:{Pending},更新行数:{statusAffectedRows},原因:{result.ErrorMsg}");
|
||||
}
|
||||
else
|
||||
{
|
||||
SetClaimedStatus(record.id, isReturned ? "退回" : "通过", null);
|
||||
_logger.Info($"OA 审批结果回写本地成功,交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},结果:{(isReturned ? "退回" : "通过")}");
|
||||
var targetStatus = isReturned ? "退回" : "通过";
|
||||
var statusAffectedRows = SetClaimedStatus(record.id, targetStatus, null);
|
||||
_logger.Info($"OA 审批结果回写本地成功,OA记录:{record.id},交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},本地审批结果:{(isReturned ? "拒绝" : "通过")},OA记录目标状态:{targetStatus},更新行数:{statusAffectedRows}");
|
||||
}
|
||||
messages.Add(record.trade_id + ":" + (string.IsNullOrWhiteSpace(result.ErrorMsg) ? (isReturned ? "退回" : "通过") : Pending));
|
||||
}
|
||||
@@ -302,7 +310,8 @@ namespace YLErp.Modules.TradeModule
|
||||
{
|
||||
if (claimed)
|
||||
{
|
||||
SetClaimedStatus(record.id, Pending, ex.Message);
|
||||
var statusAffectedRows = SetClaimedStatus(record.id, Pending, ex.Message);
|
||||
_logger.Error($"OA 轮询异常后恢复记录状态,OA记录:{record.id},requestId:{record.oa_fileid},目标状态:{Pending},更新行数:{statusAffectedRows}", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -310,7 +319,7 @@ namespace YLErp.Modules.TradeModule
|
||||
record.oa_msg = ex.Message;
|
||||
DbContext.SaveChanges();
|
||||
}
|
||||
_logger.Error($"轮询 OA 审批状态失败,requestId:{record.oa_fileid}", ex);
|
||||
_logger.Error($"轮询 OA 审批状态失败,OA记录:{record.id},交易:{record.trade_id},节点:{record.approval_process_id},requestId:{record.oa_fileid},是否已设为同步中:{claimed}", ex);
|
||||
}
|
||||
}
|
||||
_logger.Info($"OA 移动审批状态轮询结束,已处理数量:{messages.Count}");
|
||||
@@ -365,13 +374,13 @@ namespace YLErp.Modules.TradeModule
|
||||
|| trade.TradeStatus == ConsTrade.互换待复核;
|
||||
}
|
||||
|
||||
private bool TryClaimForSync(int recordId)
|
||||
private int ClaimForSync(int recordId)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
return DbContext.Database.ExecuteSqlRaw(
|
||||
"UPDATE trade_approval_oa_result SET status = {0}, last_query_time = {1} " +
|
||||
"WHERE id = {2} AND is_valid = 1 AND (status = {3} OR (status = {0} AND (last_query_time IS NULL OR last_query_time < {4})))",
|
||||
Syncing, now, recordId, Pending, now.AddMinutes(-5)) == 1;
|
||||
Syncing, now, recordId, Pending, now.AddMinutes(-5));
|
||||
}
|
||||
|
||||
private bool TryChangeStatus(int recordId, string expectedStatus, string targetStatus, string message)
|
||||
@@ -381,9 +390,9 @@ namespace YLErp.Modules.TradeModule
|
||||
targetStatus, message, recordId, expectedStatus) == 1;
|
||||
}
|
||||
|
||||
private void SetClaimedStatus(int recordId, string status, string message)
|
||||
private int SetClaimedStatus(int recordId, string status, string message)
|
||||
{
|
||||
DbContext.Database.ExecuteSqlRaw(
|
||||
return DbContext.Database.ExecuteSqlRaw(
|
||||
"UPDATE trade_approval_oa_result SET status = {0}, oa_msg = {1} WHERE id = {2} AND is_valid = 1 AND status = {3}",
|
||||
status, message, recordId, Syncing);
|
||||
}
|
||||
@@ -442,7 +451,11 @@ namespace YLErp.Modules.TradeModule
|
||||
"标的代码=" + (floatPosition?.UnderlyingCode ?? string.Empty),
|
||||
"标的名称=" + underlyingName,
|
||||
"期初收益率=" + (floatPosition?.InitYtm?.ToString("0.####%") ?? string.Empty),
|
||||
"期初价格=" + (floatPosition?.PosiNetPrice.ToString("0.####") ?? string.Empty),
|
||||
"期初价格=" + (floatPosition == null
|
||||
? string.Empty
|
||||
: (ConsGlobal.InstrumentType.IsBond(floatPosition.UnderlyingInstrumentType)
|
||||
? BondPriceConverter.ToDisplay(floatPosition.PosiNetPrice)
|
||||
: floatPosition.PosiNetPrice).ToString("0.####")),
|
||||
"行权方式=" + (trade.GetExerciseModeCn() ?? string.Empty),
|
||||
"合约期限=" + contractDays,
|
||||
"数量=" + (floatPosition?.PosiQuantity.ToString("0.####") ?? string.Empty),
|
||||
|
||||
@@ -30,16 +30,21 @@ namespace YLErp.Modules.UnderlyingModule
|
||||
public string InvestAdvisorName { get; set; }
|
||||
}
|
||||
|
||||
private const string LookupSql = @"
|
||||
//物理库名由连接串 database= 决定(DbSchema 解析),各环境库名不同(如测试环境 glms_bigdata),
|
||||
//SQL 不硬编码库名;连接开在 bigdata 数据源上,将来跨库 join ERP 主库时用 DbSchema.Of("ylcms") 同法插值
|
||||
private string BuildLookupSql()
|
||||
{
|
||||
return $@"
|
||||
SELECT
|
||||
ia.investadvisorcode AS InvestAdvisorCode,
|
||||
ia.investadvisorname AS InvestAdvisorName
|
||||
FROM bigdata.mf_fundarchives AS fa
|
||||
INNER JOIN bigdata.mf_investadvisoroutline AS ia
|
||||
FROM {DbSchema.Of("bigdata")}.mf_fundarchives AS fa
|
||||
INNER JOIN {DbSchema.Of("bigdata")}.mf_investadvisoroutline AS ia
|
||||
ON CONVERT(fa.investadvisorcode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
|
||||
CONVERT(ia.investadvisorcode USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
||||
WHERE CONVERT(fa.secucode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
|
||||
CONVERT(TRIM(SUBSTRING_INDEX(@UnderlyingCode, '.', 1)) USING utf8mb4) COLLATE utf8mb4_unicode_ci";
|
||||
}
|
||||
|
||||
public FundManagerLookupResult Lookup(string underlyingCode)
|
||||
{
|
||||
@@ -58,7 +63,7 @@ WHERE CONVERT(fa.secucode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
|
||||
try
|
||||
{
|
||||
using var connection = new MySqlConnection(connectionString);
|
||||
var matches = connection.Query<FundManagerRow>(LookupSql, new { UnderlyingCode = normalizedCode }, commandTimeout: 10)
|
||||
var matches = connection.Query<FundManagerRow>(BuildLookupSql(), new { UnderlyingCode = normalizedCode }, commandTimeout: 10)
|
||||
.Where(row => !string.IsNullOrWhiteSpace(row.InvestAdvisorName))
|
||||
.GroupBy(row => (row.InvestAdvisorCode ?? string.Empty).Trim(), StringComparer.OrdinalIgnoreCase)
|
||||
.Select(group => group.Select(row => row.InvestAdvisorName.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToArray())
|
||||
|
||||
@@ -493,12 +493,16 @@ namespace YLErp.Web.Controllers
|
||||
|
||||
//BUG-14:明细生效日期回写——前端新增行只带占位日期(2020-01-01 前后),此前不回写使新行(含模板拷贝)
|
||||
//落库 0001-01-01,被取数侧 Max(ValueDate) 分组静默丢弃;占位日期对齐本次提交行的最新生效日期,无有效行时用模板生效日期
|
||||
//明细生效日期跟主表走:主表有真实生效日期时全部行强制对齐(含复制带入的源模板行日期、行上残留的旧日期),
|
||||
//否则只改主表日期会出现"模板已生效但明细未生效"的取数断档;主表仍为占位日期时保持原兜底
|
||||
var placeholderDate = new DateTime(2020, 1, 1);
|
||||
var effectiveValueDate = marginTemplate.Details
|
||||
.Where(d => d.ValueDate > placeholderDate)
|
||||
.Select(d => d.ValueDate)
|
||||
.DefaultIfEmpty(marginTemplate.ValueDate > placeholderDate ? marginTemplate.ValueDate : placeholderDate)
|
||||
.Max();
|
||||
var detailValueDate = marginTemplate.ValueDate > placeholderDate
|
||||
? marginTemplate.ValueDate
|
||||
: marginTemplate.Details
|
||||
.Where(d => d.ValueDate > placeholderDate)
|
||||
.Select(d => d.ValueDate)
|
||||
.DefaultIfEmpty(placeholderDate)
|
||||
.Max();
|
||||
|
||||
marginTemplate.Details.ForEach(x =>
|
||||
{
|
||||
@@ -509,7 +513,7 @@ namespace YLErp.Web.Controllers
|
||||
detail = new margin_template_detail();
|
||||
yldb.margin_template_detail.Add(detail);
|
||||
}
|
||||
detail.ValueDate = x.ValueDate > placeholderDate ? x.ValueDate : effectiveValueDate;
|
||||
detail.ValueDate = detailValueDate;
|
||||
detail.MarginTemplateId = marginTemplate.id;
|
||||
detail.GroupRatio = x.GroupRatio;
|
||||
detail.MarginRatio1 = x.MarginRatio1;
|
||||
|
||||
@@ -32,23 +32,14 @@
|
||||
};
|
||||
var pageData = new
|
||||
{
|
||||
showCCR = PS.Config.Company == CompanyEnum.国海,
|
||||
is厦门象屿 = PS.Config.Company == CompanyEnum.厦门象屿,
|
||||
showCCR = false, // 国联恒 false(原 Company == 国海 定制分支)
|
||||
is厦门象屿 = false, // 国联恒 false(原 Company == 厦门象屿 定制分支)
|
||||
ShowMultiplier = PS.Config.ErpElement.AccumulatorShowMultiplier1,
|
||||
};
|
||||
var calcQuotas = CalcPriceShowConfigService.GetShowCalcQuota();
|
||||
}
|
||||
@section CSS{
|
||||
<link href="~/Style/Css/pricing.structure.css?v=1" rel="stylesheet" />
|
||||
@switch (PS.Config.Company)
|
||||
{
|
||||
case CompanyEnum.光大光子:
|
||||
<link href="~/Style/GDGZ/pricing.structure.css" rel="stylesheet" />
|
||||
break;
|
||||
case CompanyEnum.国泰君安:
|
||||
<link href="~/Style/GTJA/pricing.structure.css" rel="stylesheet" />
|
||||
break;
|
||||
}
|
||||
<link href="~/Style/Css/settag.css?v=@(HtmlUtil.JsVersion)" rel="stylesheet" />
|
||||
<style>
|
||||
.pitem-cash:not(.pitem) { background: #f0f8ff }
|
||||
@@ -103,44 +94,6 @@
|
||||
ylotc.structureTypes = pageObj.structureTypes;
|
||||
</script>
|
||||
|
||||
@if (PS.Config.Company == CompanyEnum.伴兴)
|
||||
{
|
||||
<script>
|
||||
pageVue.GetTotalMargin = function (trades, structureType) {
|
||||
if (trades.length === 1) return parseFloat(trades[0].InitialMargin) || 0;
|
||||
if (!structureType || structureType === "结构化交易") {
|
||||
return _.reduce(trades, (acc, cur) => acc += parseFloat(cur.InitialMargin) || 0, 0);
|
||||
}
|
||||
if (structureType.indexOf("跨式") >= 0) {
|
||||
return _.max(_.map(trades, x => Math.abs(x.InitialMargin)));
|
||||
}
|
||||
return _.reduce(trades, (acc, cur) => acc += cur.BuySell === '卖出' ? 0 : parseFloat(cur.InitialMargin) || 0, 0);
|
||||
};
|
||||
</script>
|
||||
}
|
||||
else if (PS.Config.Company == CompanyEnum.茂川资本)
|
||||
{
|
||||
<script>
|
||||
pageVue.GetTotalMargin = function (trades, structureType) {
|
||||
if (structureType && structureType.indexOf("跨式") >= 0) {
|
||||
return _.max(_.map(trades, x => Math.abs(x.InitialMargin)));
|
||||
}
|
||||
return _.reduce(trades, (acc, cur) => acc += parseFloat(cur.InitialMargin) || 0, 0);
|
||||
};
|
||||
</script>
|
||||
}
|
||||
else if (PS.Config.Company == CompanyEnum.弘业)
|
||||
{
|
||||
<script>
|
||||
pageVue.GetTotalMargin = function (trades, structureType) {
|
||||
if (structureType && structureType.indexOf("跨式") >= 0) {
|
||||
return _.max(_.map(trades, x => Math.abs(x.InitialMargin)));
|
||||
}
|
||||
return _.reduce(trades, (acc, cur) => acc += parseFloat(cur.InitialMargin) || 0, 0);
|
||||
};
|
||||
</script>
|
||||
}
|
||||
|
||||
<script src="~/Scripts/app/pricing/tradePricing.js?v=@HtmlUtil.JsVersion"></script>
|
||||
<script src="~/Scripts/app/pricing/structure.js?v=@(HtmlUtil.JsVersion)"></script>
|
||||
}
|
||||
@@ -635,35 +588,6 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="width:80px;border-bottom:1px solid red;"><div style="width:80px;font-weight:700;padding-left:10px;border-left:4px solid red">其他要素</div></div>
|
||||
@if (PS.Config.Company == CompanyEnum.东方财富)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label class="formlabel">初始预付金率</label>
|
||||
<input type="number" class="form-input" id="InitialAdvance" />
|
||||
<span>% </span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="formlabel">期间预付金率</label>
|
||||
<input type="number" class="form-input" id="PeriodAdvance" />
|
||||
<span>% </span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="formlabel">前端收益费率</label>
|
||||
<input type="number" class="form-input" id="FontEarning" />
|
||||
<span>% </span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="formlabel">追保线</label>
|
||||
<input type="number" class="form-input" id="ConfirmedLine" />
|
||||
<span>% </span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="formlabel">追保比例下限</label>
|
||||
<input type="number" class="form-input" id="ConfirmedFloor" />
|
||||
<span>% </span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurUser.交易管理_交易标签调整权限)
|
||||
{
|
||||
<div class='form-group'>
|
||||
@@ -895,35 +819,6 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="width:80px;border-bottom:1px solid red;"><div style="width:80px;font-weight:700;padding-left:10px;border-left:4px solid red">其他要素</div></div>
|
||||
@if (PS.Config.Company == CompanyEnum.东方财富)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label class="formlabel">初始预付金率</label>
|
||||
<input type="number" class="form-input" id="accumulatorInitialAdvance" />
|
||||
<span>% </span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="formlabel">期间预付金率</label>
|
||||
<input type="number" class="form-input" id="accumulatorPeriodAdvance" />
|
||||
<span>% </span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="formlabel">前端收益费率</label>
|
||||
<input type="number" class="form-input" id="accumulatorFontEarning" />
|
||||
<span>% </span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="formlabel">追保线</label>
|
||||
<input type="number" class="form-input" id="accumulatorConfirmedLine" />
|
||||
<span>% </span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="formlabel">追保比例下限</label>
|
||||
<input type="number" class="form-input" id="accumulatorConfirmedFloor" />
|
||||
<span>% </span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurUser.交易管理_交易标签调整权限)
|
||||
{
|
||||
<div class='form-group'>
|
||||
|
||||
@@ -910,33 +910,6 @@
|
||||
$("#modalChooseContractType").modal('hide');
|
||||
}
|
||||
|
||||
function sendReport_guojun() {
|
||||
var clientIds = $("#ClientId").val() == null ? "" : $("#ClientId").val().join(",");
|
||||
var contractTypes = ContractTypeCtrl.getValue();
|
||||
var data = { UnWindDateStart: $("#DateFromUnWindDate").val(), UnWindDateEnd: $("#DateToUnWindDate").val(), ClientIds: clientIds ,receiver : contractTypes};
|
||||
var jgrid = jQuery('#listGrid');
|
||||
var ids = main.GetGridIds(jgrid);
|
||||
if (ids.length == 0) {
|
||||
if (data.UnWindDateStart == undefined || data.UnWindDateStart == null || data.UnWindDateStart == ""
|
||||
|| data.UnWindDateEnd == undefined || data.UnWindDateEnd == null || data.UnWindDateEnd == "") {
|
||||
showmiddlemessage("发送结算确认书需要选择确切的实际行权日区间!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else { data.TradeCashIds = ids.join(","); }
|
||||
if (ids.length == 0 && clientIds == "") {
|
||||
main.confirm("确定发送所有结算确认书?可选择客户名称单个发送", function () {
|
||||
main.post("/trade/SendEmailSettleBill_Guojun", data).done(function (data) {
|
||||
});
|
||||
})
|
||||
}
|
||||
else {
|
||||
main.post("/trade/SendEmailSettleBill_Guojun", data).done(function (data) {
|
||||
});
|
||||
}
|
||||
$("#modalChooseContractType").modal('hide');
|
||||
}
|
||||
|
||||
function sentMail(id) {
|
||||
ReportId = id;
|
||||
var clientIds = $("#ClientId").val();
|
||||
@@ -1161,14 +1134,7 @@
|
||||
//@MyControls.Btn("客户行权日报告", "ClientMaturityTrade()")
|
||||
}
|
||||
@MyControls.Btn("批量下载结算确认书", "downloadMaturityReports()")
|
||||
@if (PS.Config.IsGuoJun)
|
||||
{
|
||||
@MyControls.Btn("发送结算确认书", "sendReport_guojun()")
|
||||
}
|
||||
else
|
||||
{
|
||||
@MyControls.Btn("发送结算确认书", "SendEmailSettleBill()")
|
||||
}
|
||||
@MyControls.Btn("发送结算确认书", "SendEmailSettleBill()")
|
||||
@if (isAutoSealAndUploadFiles && !isAutoSealAfterGenerateBook)
|
||||
{
|
||||
@MyControls.Btn("批量确认书用印", "BatchSealContracts()")
|
||||
@@ -1187,4 +1153,4 @@
|
||||
-->
|
||||
@Html.Raw(JqGridSimple.OutTable())
|
||||
|
||||
@await Html.PartialAsync("/Views/Common/_chooseContractType.cshtml", PS.Config.IsGuoJun ? "sendReport_guojun()" : "sendReport()")
|
||||
@await Html.PartialAsync("/Views/Common/_chooseContractType.cshtml", "sendReport()")
|
||||
@@ -71,11 +71,4 @@
|
||||
@Html.Raw(JqGridSimple.OutTable())
|
||||
|
||||
|
||||
@if (PS.Config.IsGuoJun)
|
||||
{
|
||||
@await Html.PartialAsync("/Views/Common/_chooseContractType.cshtml", "sendReportGuojun()")
|
||||
}
|
||||
else
|
||||
{
|
||||
@await Html.PartialAsync("/Views/Common/_chooseContractType.cshtml", "sendReport()")
|
||||
}
|
||||
@await Html.PartialAsync("/Views/Common/_chooseContractType.cshtml", "sendReport()")
|
||||
@@ -10,7 +10,8 @@
|
||||
"ylcms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"yladmin": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"ylclient": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"bondoms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
|
||||
"bondoms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;",
|
||||
"bigdata": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_bigdata;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;"
|
||||
},
|
||||
"AppSettings": {
|
||||
"VirtualPathRoot": "",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* percentColumnText.js — 交易要素列的百分比展示文本(纯函数)
|
||||
* ============================================================================
|
||||
* 把散落在 structure.js ×3 / structure_dz.js / tradeEditV2.js / tradeEditGroup.js
|
||||
* 把散落在 structure.js ×3 / tradeEditV2.js / tradeEditGroup.js
|
||||
* 的六处逐字相同一行式显式化为命名函数。
|
||||
* 【约定】ColumnType==4 = 百分比列:值以小数形式存储(如 0.05),展示时 ×100 追加 %;
|
||||
* 若值文本已含 %(人工录入或后端已格式化)则原样展示,不做二次换算;
|
||||
|
||||
@@ -710,43 +710,6 @@ function sendReport() {
|
||||
$("#modalChooseContractType").modal('hide');
|
||||
}
|
||||
|
||||
function sendReportGuojun() {
|
||||
var clientIds = $("#ClientId").val() == null ? "" : $("#ClientId").val().join(",");
|
||||
var contractTypes = ContractTypeCtrl.getValue();
|
||||
var data = { UnWindDateStart: $("#DateFromUnWindDate").val(), UnWindDateEnd: $("#DateToUnWindDate").val(), ClientIds: clientIds, receiver: contractTypes };
|
||||
var jgrid = jQuery('#listGrid');
|
||||
var rowIds = jgrid.jqGrid('getGridParam', 'selarrrow');
|
||||
var ids = [];
|
||||
for (var i = 0; i < rowIds.length; i++) {
|
||||
var rowObjs = jgrid.getRowData();
|
||||
for (var j = 0; j < rowObjs.length; j++) {
|
||||
if (rowObjs[j]["trade_cash.id"] == rowIds[i]) {
|
||||
ids.push(rowObjs[j]["trade_cash.id"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ids.length == 0) {
|
||||
if (data.UnWindDateStart == undefined || data.UnWindDateStart == null || data.UnWindDateStart == ""
|
||||
|| data.UnWindDateEnd == undefined || data.UnWindDateEnd == null || data.UnWindDateEnd == "") {
|
||||
showmiddlemessage("发送清算确认书需要选择确切的实际终止日区间!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else { data.TradeCashIds = ids.join(","); }
|
||||
if (ids.length == 0 && clientIds == "") {
|
||||
main.confirm("确定发送所有提前终止确认书?可选择客户名称单个发送", function () {
|
||||
main.post("/trade/SendEmailUnwindReportGuojun", data).done(function (data) {
|
||||
});
|
||||
})
|
||||
}
|
||||
else {
|
||||
main.post("/trade/SendEmailUnwindReportGuojun", data).done(function (data) {
|
||||
});
|
||||
}
|
||||
$("#modalChooseContractType").modal('hide');
|
||||
}
|
||||
|
||||
function SearchClick(isSearchclick) {
|
||||
var listGrid = $('#listGrid');
|
||||
|
||||
|
||||
@@ -40,7 +40,10 @@ namespace RealTimeCalcPositionService
|
||||
{
|
||||
string calctime = memoryCache.Get<string>("calctime");
|
||||
var currentTime = DateTime.Now;
|
||||
bool hasNew= RealtimePnlCalc.HasNewFlow(currentTime);
|
||||
//以上次计算时间为基准判断是否有新流水,避免无变化时空转重算
|
||||
var hasNew = DateTime.TryParseExact(calctime, "yyyy-MM-dd HH:mm:ss.fff",
|
||||
System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out var lastCalcTime)
|
||||
&& RealtimePnlCalc.HasNewFlow(lastCalcTime);
|
||||
if (string.IsNullOrEmpty(calctime)|| hasNew)
|
||||
{
|
||||
memoryCache.Set("calctime", currentTime.ToString("yyyy-MM-dd HH:mm:ss.fff"), TimeSpan.FromSeconds(30));
|
||||
@@ -53,6 +56,7 @@ namespace RealTimeCalcPositionService
|
||||
_logger.Error(ex, "实时持仓服务异常:" + ex.Message);
|
||||
}
|
||||
}
|
||||
stoppingToken.WaitHandle.WaitOne(TimeSpan.FromSeconds(2));
|
||||
}
|
||||
}
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
|
||||
@@ -48,6 +48,8 @@ namespace RealTimeCalcPositionService
|
||||
_logger.Error(ex, "实时预付金计算异常:" + ex.Message);
|
||||
}
|
||||
|
||||
//估值数据盘中不变、交易另有事件触发,无需连续轮询
|
||||
stoppingToken.WaitHandle.WaitOne(TimeSpan.FromSeconds(30));
|
||||
}
|
||||
}
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
|
||||
Reference in New Issue
Block a user