diff --git a/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看多】-【债券ETF】-清洁版.docx b/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看多】-【债券ETF】-清洁版.docx index 3233881e..e99377d3 100644 Binary files a/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看多】-【债券ETF】-清洁版.docx and b/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看多】-【债券ETF】-清洁版.docx differ diff --git a/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看空】-【债券ETF】-清洁版.docx b/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看空】-【债券ETF】-清洁版.docx index e6ad31f5..423071ed 100644 Binary files a/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看空】-【债券ETF】-清洁版.docx and b/Plugins/YLErp.Plugins.GuoLian/App_Docs/contract_template/国联民生-收益互换交易确认书-境内模板-【客户看空】-【债券ETF】-清洁版.docx differ diff --git a/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs b/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs index c0cd3c47..d491bf73 100644 --- a/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs +++ b/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs @@ -326,6 +326,8 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator dic["IsFixed"] = "□"; dic["IsFloat"] = "□"; + dic["期初观察日"] = trade.StartDate?.ToString("yyyy年M月d日") ?? ""; + dic["期末观察日"] = trade.ExerciseDate?.ToString("yyyy年M月d日") ?? ""; if (interestMargin != null) { if (string.IsNullOrEmpty(interestMargin.FloatRateUnderlyingCode)) @@ -343,6 +345,13 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator dic["利差"] = ((double)interestMargin.InterestRateDefault * 10000).ToString("N0"); dic["重置频率"] = (interestMargin.interest_rest_days ?? 0) + "天"; } + if (interestMargin.SwapIntervalList.Count()>0) + { + 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["期末观察日"] = firstInterval.Date.ToString("yyyy年M月d日") ?? ""; + } } else { diff --git a/YLErpDAL/Modules/SystemModule/ClientEditConfigService.cs b/YLErpDAL/Modules/SystemModule/ClientEditConfigService.cs index a2868852..0cb6db5f 100644 --- a/YLErpDAL/Modules/SystemModule/ClientEditConfigService.cs +++ b/YLErpDAL/Modules/SystemModule/ClientEditConfigService.cs @@ -612,6 +612,8 @@ namespace YLErp.Modules.SystemModule /// public string dictionaryKey { get; set; } + public int? maxLength { get; set; } + public override string ToString() { return $"{name}-{label}-{type}"; diff --git a/YLErpDAL/Modules/TradeModule/DealModule/TradeContractGenerateService.cs b/YLErpDAL/Modules/TradeModule/DealModule/TradeContractGenerateService.cs index bac3c424..82428458 100644 --- a/YLErpDAL/Modules/TradeModule/DealModule/TradeContractGenerateService.cs +++ b/YLErpDAL/Modules/TradeModule/DealModule/TradeContractGenerateService.cs @@ -273,7 +273,10 @@ namespace YLErp.Modules.TradeModule.DealModule logger.Info($"[groupGenerateHandle_Date] 未找到客户信息 - ClientId: {firstTrade.ClientId}"); return; } - + if (string.IsNullOrEmpty(client.Code)) + { + throw new ServiceException($"对手方代码缩写缺失,请联系运营组同事维护: {firstTrade.ClientName}"); + } var supProtocolDate = client.SupProtocolDate; logger.Info($"[groupGenerateHandle_Date] 客户补充协议日期: {supProtocolDate}"); diff --git a/YLErpDAL/Modules/TradeModule/DocGenerateModule/ConfirmationGenerateContext.cs b/YLErpDAL/Modules/TradeModule/DocGenerateModule/ConfirmationGenerateContext.cs index 7894e15b..050fa800 100644 --- a/YLErpDAL/Modules/TradeModule/DocGenerateModule/ConfirmationGenerateContext.cs +++ b/YLErpDAL/Modules/TradeModule/DocGenerateModule/ConfirmationGenerateContext.cs @@ -1347,6 +1347,9 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule return Trade.TradeNumber; } + /// + /// 生成国贸确认书编号 + /// private string GenerateGuolianContractNo() { if (_contractType != ContractTypeEnum.Trade) @@ -1355,13 +1358,11 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule } if (Trade.TradeType == "收益互换") { - var posi = GetSwapPositions(Trade.id, true).FirstOrDefault(p => p.PositionType > 0); - var isLong = posi?.PositionType == (int)PositionTypeFlag.Short ? false : true; - - return "GLMS-" + _client.Number + "-" + Trade.TradeNumber; + return GuolianContractNoGenerator.Generate(DbContext, (trade)Trade, _client.Code); } - return Trade.TradeNumber; + return Trade.TradeNumber; } + /// /// 生成国贸确认书编号 /// diff --git a/YLErpDAL/Modules/TradeModule/DocGenerateModule/GuolianContractNoGenerator.cs b/YLErpDAL/Modules/TradeModule/DocGenerateModule/GuolianContractNoGenerator.cs new file mode 100644 index 00000000..18e0312b --- /dev/null +++ b/YLErpDAL/Modules/TradeModule/DocGenerateModule/GuolianContractNoGenerator.cs @@ -0,0 +1,174 @@ +using YLErp.BLL; +using YLErp.DBModels; +using YLErp.DBModels.Consts; +using YLErp.DBModels.Enums; + +namespace YLErp.Modules.TradeModule.DocGenerateModule +{ + /// + /// 国贸交易确认书编号生成器 + /// 规则: + /// - 对客交易(OpponentRole为"甲方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-{标的代码}-{当日第N笔对客交易(数字递增)},从001开始 + /// - 非对客交易(OpponentRole为"乙方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-{标的代码}-{当日第N笔非对客交易(字母递增)},从A开始 + /// + public class GuolianContractNoGenerator + { + private static readonly object _syncLock = new object(); + + /// + /// 生成国贸交易确认书编号 + /// + /// 数据库上下文 + /// 交易记录 + /// 客户代码 + /// 确认书编号 + public static string Generate( + YLContext dbContext, + trade trade, + string clientCode) + { + // 根据trade的OpponentRole判断是否为对客交易 + var isClientTrade = trade.OpponentRole == "甲方"; + var underlyingCode = trade.UnderlyingCode ?? ""; + var tradeDate = trade.TradeDate ?? DateTime.MinValue; + var tradeDateStr = tradeDate.ToString("yyyyMMdd"); + var prefix = $"GLMS-{clientCode}-{tradeDateStr}-{underlyingCode}-"; + + lock (_syncLock) + { + var existingForTrade = dbContext.trade_contract_r + .Where(r => r.TradeId == trade.id && r.Type == ContractTypeEnum.Trade && r.IsValid) + .Select(r => r.ContractCode) + .FirstOrDefault(); + + if (!string.IsNullOrWhiteSpace(existingForTrade)) + { + return existingForTrade; + } + + var existingCodes = dbContext.trade_contract_r + .Where(d => d.ContractCode.StartsWith(prefix) && d.Type == ContractTypeEnum.Trade && d.IsValid) + .Select(d => d.ContractCode) + .ToList(); + + var existingCodeSet = new HashSet(); + for (var i = 0; i < existingCodes.Count; i++) + { + var code = existingCodes[i]; + if (!string.IsNullOrWhiteSpace(code)) + { + existingCodeSet.Add(code); + } + } + + if (isClientTrade) + { + 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) + { + continue; + } + + var suffix = code.Substring(prefixLen); + if (int.TryParse(suffix, out var n) && n > maxNo) + { + maxNo = n; + } + } + + var nextNo = maxNo + 1; + var contractNo = $"{prefix}{nextNo:D3}"; + while (existingCodeSet.Contains(contractNo)) + { + nextNo++; + contractNo = $"{prefix}{nextNo:D3}"; + } + return contractNo; + } + else + { + 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) + { + continue; + } + + var suffix = code.Substring(prefixLen); + if (!TryParseLettersToNumber(suffix, out var n)) + { + continue; + } + + if (n > maxNo) + { + maxNo = n; + } + } + + var nextNo = maxNo + 1; + var contractNo = $"{prefix}{NumberToLetter(nextNo)}"; + while (existingCodeSet.Contains(contractNo)) + { + nextNo++; + contractNo = $"{prefix}{NumberToLetter(nextNo)}"; + } + return contractNo; + } + } + } + + private static bool TryParseLettersToNumber(string letters, out int number) + { + number = 0; + if (string.IsNullOrEmpty(letters)) + { + return false; + } + + for (var i = 0; i < letters.Length; i++) + { + var c = letters[i]; + if (c >= 'a' && c <= 'z') + { + c = (char)(c - 32); + } + else if (c < 'A' || c > 'Z') + { + number = 0; + return false; + } + + number = (number * 26) + (c - 'A' + 1); + } + return true; + } + + /// + /// 将数字转换为字母序列(1=A, 2=B, 26=Z, 27=AA, 28=AB...) + /// + private static string NumberToLetter(int number) + { + if (number <= 0) + { + return "A"; + } + + string result = ""; + while (number > 0) + { + number--; // 调整为从0开始 + result = (char)('A' + (number % 26)) + result; + number /= 26; + } + return result; + } + } +} diff --git a/YLErpWeb/Views/client/_ClientEditFields.cshtml b/YLErpWeb/Views/client/_ClientEditFields.cshtml index c8c5a4f2..f8065a25 100644 --- a/YLErpWeb/Views/client/_ClientEditFields.cshtml +++ b/YLErpWeb/Views/client/_ClientEditFields.cshtml @@ -95,11 +95,19 @@ - + } else { - + if (f.maxLength.HasValue) + { + + } + else + { + + } + } @if (f.required) {