using System.Collections.Generic; using System.Linq; using BaseOUDAL; using DocumentFormat.OpenXml.Bibliography; using NPOI.POIFS.NIO; using YLErp.Helpers; namespace YLErp.Modules.EodModule { /// /// 日终价格服务 /// public class EodPriceService : YLBaseService { public EodPriceService(OptUserInfo userInfo) : base(userInfo) { } public SearchListResult SearchUnderlyingList(EodCommodityFuturePriceReq req) { var valueDtStart = req.ValueDateStart.Year > 2000 ? req.ValueDateStart : DateTime.Today.AddYears(-1); var valueDtEnd = req.ValueDateEnd.Year > 2000 ? req.ValueDateEnd.AddDays(1) : DateTime.Today.AddYears(1); var predicatUn = PredicateBuilder.Create(d => d.LaunchState == "1"); var predicatEoc = PredicateBuilder.Create(source => source.ValueDate >= valueDtStart && source.ValueDate < valueDtEnd); var predicatEot = PredicateBuilder.Create(source => source.ValueDate >= valueDtStart && source.ValueDate < valueDtEnd); var predicatEob = PredicateBuilder.Create(source => source.valuation_date >= valueDtStart && source.valuation_date < valueDtEnd); if (!string.IsNullOrEmpty(req.DataSource)) { predicatEoc = predicatEoc.And(d => d.DataSource.Contains(req.DataSource)); predicatEot = predicatEot.And(d => d.DataSource.Contains(req.DataSource)); // 债券来源恒为"中债估值"(聚源仅转发,无人手工维护), // 只有按"中债估值"筛选时才命中债券;人工/系统筛选不命中债券。 if (req.DataSource != EodPriceBase.中债估值) { predicatEob = predicatEob.And(d => false); } } if (!string.IsNullOrEmpty(req.MarketName)) { predicatUn = predicatUn.And(d => d.MarketName == req.MarketName); } if (!string.IsNullOrEmpty(req.UnderlyingCode)) { predicatUn = predicatUn.And(d => d.UnderlyingCode.Contains(req.UnderlyingCode)); } var queryUn = DbContext.underlying_manager.Where(predicatUn).Select(n => new { n.id, n.LaunchState, n.MarketName, n.UnderlyingState, n.UnderlyingType, n.UnderlyingCode, n.UnderlyingName,n.UnderlyingInstrumentType }); var query1 = from un in queryUn join source in DbContext.eod_commodity_future_price.Where(predicatEoc) on un.id equals source.UnderlyingId select new EodUnderlyingPriceDto { IsBond=false, id = source.id, DataSource = source.DataSource, LaunchState = un.LaunchState, MarketName = un.MarketName, UnderlyingId = un.id, UnderlyingCode = un.UnderlyingCode, UnderlyingName = un.UnderlyingName, UnderlyingState = un.UnderlyingState, UnderlyingType = un.UnderlyingType, UnderlyingInstrumentType = "CommodityFutures", RealInstrumentType = un.UnderlyingInstrumentType, ValueDate = source.ValueDate, SettlePrice = source.SettlePrice, ClosePrice = source.ClosePrice, UpdateTime = source.OptDate, ReferencePrice = source.ReferencePrice, SourceTime = source.SourceTime, DeciClosePrice=0, DeciSettlePrice = 0, DeciReferencePrice=0 }; var query2 = from un in queryUn join stockClose in DbContext.eod_stock_price.Where(predicatEot) on un.UnderlyingCode equals stockClose.UnderlyingCode select new EodUnderlyingPriceDto { IsBond = false, id = stockClose.id, DataSource = stockClose.DataSource, LaunchState = un.LaunchState, MarketName = un.MarketName, UnderlyingId = un.id, UnderlyingCode = un.UnderlyingCode, UnderlyingName = un.UnderlyingName, UnderlyingState = un.UnderlyingState, UnderlyingType = un.UnderlyingType, UnderlyingInstrumentType = "Stock", RealInstrumentType = un.UnderlyingInstrumentType, ValueDate = stockClose.ValueDate, SettlePrice = stockClose.ClosePrice, ClosePrice = stockClose.ClosePrice, UpdateTime = stockClose.OptDate, ReferencePrice = stockClose.ReferencePrice, SourceTime = stockClose.SourceTime, DeciClosePrice = 0, DeciSettlePrice = 0, DeciReferencePrice = 0 }; var query3 = from un in queryUn join bondClose in DbContext.china_bond_valuation.Where(predicatEob) on un.UnderlyingCode equals bondClose.bond_id select new EodUnderlyingPriceDto { IsBond = true, id = bondClose.id, UpdateUser = bondClose.update_user, // 债券 DataSource 在后处理统一置为固定值"中债估值"(见下方 foreach)。 LaunchState = un.LaunchState, MarketName = un.MarketName, UnderlyingId = un.id, UnderlyingCode = un.UnderlyingCode, UnderlyingName = un.UnderlyingName, UnderlyingState = un.UnderlyingState, UnderlyingType = un.UnderlyingType, UnderlyingInstrumentType = un.UnderlyingInstrumentType, RealInstrumentType = un.UnderlyingInstrumentType, ValueDate = bondClose.valuation_date, SettlePrice=0, DeciSettlePrice =bondClose.net_price, ClosePrice=0, DeciClosePrice = bondClose.dirty_price_close, UpdateTime = bondClose.update_time, ReferencePrice=0, DeciReferencePrice = bondClose.yield, SourceTime="" }; var unionQuery = query1.Concat(query2); var finalQuery = unionQuery.Concat(query3); if (string.IsNullOrEmpty(req.sidx)) { req.sidx = "ValueDate"; req.sord = "desc"; } var result = finalQuery.ToSearchList(req); // 解析手工改过估值的债券操作人姓名:update_user 非空 = 被人手工改过(聚源同步为 NULL)。 // 此时来源列显示改这个人名,而非"中债估值",以便溯源到具体操作人。 var manualBondRows = result.rows.Where(r => r.IsBond && r.UpdateUser.HasValue).ToList(); Dictionary operatorNameMap = null; if (manualBondRows.Any()) { var operatorIds = manualBondRows.Select(r => r.UpdateUser.Value).Distinct().ToList(); using (var erpCtx = DbContextFactory.GetErpBaseContext()) { operatorNameMap = erpCtx.SystemUsers.AsNoTracking() .Where(u => operatorIds.Contains((long)u.Id)) .ToDictionary(u => (long)u.Id, u => u.Name); } } foreach (var item in result.rows) { if (item.IsBond) { // 人工改动过则显示改这个人名,否则显示"中债估值"。 item.DataSource = ResolveBondDisplaySource(item.UpdateUser, operatorNameMap); item.SourceTime = item.UpdateTime.HasValue? item.UpdateTime.Value.ToString("yyyy-MM-dd HH:mm:ss"):""; item.SettlePrice=Convert.ToDouble(item.DeciSettlePrice); item.ClosePrice = Convert.ToDouble(item.DeciClosePrice); item.ReferencePrice = Convert.ToDouble(item.DeciReferencePrice); } } return result; } /// /// 保存日终期货价格 /// public eod_commodity_future_price SaveEodFuturePrice(eod_commodity_future_price req) { if (req is null) { throw new ArgumentNullException(nameof(req)); } if (DbContext.eod_commodity_future_price.Any(n => n.id != req.id && n.UnderlyingCode == req.UnderlyingCode && n.ValueDate == req.ValueDate)) { throw new ServiceException("已存在相同估值日期,相同合约的数据"); } eod_commodity_future_price dbmodel; if (req.id == 0) { DbContext.eod_commodity_future_price.Add(dbmodel = req); } else { dbmodel = DbContext.eod_commodity_future_price.Find(req.id); if (dbmodel == null) { throw new ServiceException("数据不存在"); } UpdateChanges(dbmodel, req); } SetDBModelOpt(dbmodel); dbmodel.DataSource = EodPriceBase.人工; DbContext.SaveChanges(); return dbmodel; } public ChinaBondValuation SaveBondPrice(ChinaBondValuation req) { if (req is null) { throw new ArgumentNullException(nameof(req)); } ChinaBondValuation dbmodel; if (req.id == 0) { DbContext.china_bond_valuation.Add(dbmodel = req); } else { dbmodel = DbContext.china_bond_valuation.Find(req.id); if (dbmodel == null) { throw new ServiceException("数据不存在"); } UpdateChanges(dbmodel, req); } // 记录手工编辑人:写入登录用户ID到已有列(create_user/update_user), // 不新增字段。聚源同步路径(SettlementPriceImportService)不写这两列,故 NULL 即"自动同步"。 StampBondOperator(dbmodel, UserId, req.id == 0); dbmodel.update_time = DateTime.Now; DbContext.SaveChanges(); return dbmodel; } /// /// 标记债券估值(china_bond_valuation)的操作人。 /// 该表已有 create_user/update_user 两列(bigint),但聚源同步路径不写入, /// 因此:NULL = 聚源/中债自动同步;有值 = 被人手工编辑(记录登录用户ID)。 /// 抽出为纯静态函数,供 SaveBondPrice 与单元测试共用。 /// /// 债券估值实体 /// 当前登录用户ID /// 是否为新增(true 时同时写 create_user) public static void StampBondOperator(ChinaBondValuation model, int userId, bool isNew) { model.update_user = userId; if (isNew) { model.create_user = userId; } } /// /// 债券来源列该显示什么: /// - update_user 有值(被人手工改过)且能解析到操作人姓名 → 显示改这个人名,便于溯源; /// - 否则(聚源/中债自动同步,update_user 为 NULL)→ 固定显示"中债估值"。 /// 抽为纯静态函数,与解析姓名所需的 sys_user 查询解耦,便于无库单元测试。 /// /// china_bond_valuation.update_user(NULL = 自动同步) /// update_user(ID) → 操作人姓名 的字典(由调用方按需从 sys_user 解析) public static string ResolveBondDisplaySource(long? updateUser, IDictionary operatorNameMap) { if (updateUser.HasValue && operatorNameMap != null && operatorNameMap.TryGetValue(updateUser.Value, out var opName) && !string.IsNullOrEmpty(opName)) { return opName; } return EodPriceBase.中债估值; } /// /// 保存日终股票价格 /// public eod_stock_price SaveEodStockPrice(eod_stock_price req) { if (req is null) { throw new ArgumentNullException(nameof(req)); } if (DbContext.eod_stock_price.Any(n => n.id != req.id && n.UnderlyingCode == req.UnderlyingCode && n.ValueDate == req.ValueDate)) { throw new ServiceException("已存在相同估值日期,相同合约的数据"); } eod_stock_price dbmodel; if (req.id == 0) { DbContext.eod_stock_price.Add(dbmodel = req); } else { dbmodel = DbContext.eod_stock_price.Find(req.id); if (dbmodel == null) { throw new ServiceException("数据不存在"); } UpdateChanges(dbmodel, req); } SetDBModelOpt(dbmodel); dbmodel.DataSource = EodPriceBase.人工; DbContext.SaveChanges(); return dbmodel; } } /// /// /// public class EodCommodityFuturePriceReq : BaseSearchReq { /// /// 数据来源 /// public string DataSource { get; set; } public string MarketName { get; set; } public string LaunchState { get; set; } /// /// 标的代码 /// public string UnderlyingCode { get; set; } public DateTime ValueDateStart { get; set; } public DateTime ValueDateEnd { get; set; } } public class EodUnderlyingPriceDto { public string EncryptId { get { return DataProtectHelper.Encrypt(id.ToString()); } } public long id { get; set; } public DateTime ValueDate { get; set; } public double SettlePrice { get; set; } public double ClosePrice { get; set; } public double? ReferencePrice { get; set; } public string DataSource { get; set; } public string UnderlyingType { get; set; } public string UnderlyingInstrumentType { get; set; } /// /// 真实标的种类(取自 underlying_manager),仅供列表"标的种类"列显示。 /// UnderlyingInstrumentType 仍作为"存储表路由键"使用,二者解耦,避免改动历史路由逻辑。 /// public string RealInstrumentType { get; set; } public string UnderlyingInstrumentTypeCn => ConsGlobal.InstrumentType.GetDesc(RealInstrumentType ?? UnderlyingInstrumentType); public string UnderlyingState { get; set; } public string MarketName { get; set; } public string LaunchState { get; set; } public int UnderlyingId { get; set; } public string UnderlyingCode { get; set; } public string UnderlyingName { get; set; } public DateTime? UpdateTime { get; set; } public string SourceTime { get; set; } public decimal? DeciSettlePrice { get; set; } public decimal? DeciClosePrice { get; set; } public decimal? DeciReferencePrice { get; set; } public bool IsBond { get; set; } /// /// 手工改过估值时的操作人ID(china_bond_valuation.update_user)。 /// NULL = 聚源/中债自动同步(无人手工维护);有值 = 被人手工改过、可溯源。 /// 仅债券行可能非空,用于列表来源列显示改这个人名而非"中债估值"。 /// public long? UpdateUser { get; set; } } }