using YLErp.BLL;
using YLErp.DBModels;
using YLErp.DBModels.Consts;
using YLErp.DBModels.Enums;
namespace YLErp.Modules.TradeModule.DocGenerateModule
{
///
/// 国贸交易确认书编号生成器
/// 规则:
/// - 对客交易(OpponentRole为"甲方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-FICC-{当日第N笔对客交易(数字递增)}-{标的代码},从01开始
/// 例:GLMS-IS-20260424-01-250210IB
/// - 非对客交易(OpponentRole为"乙方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-FICC-{当日第N笔非对客交易使用字母递增}-{标的代码},从A开始
/// 例:GLMS-IS-20260424-A-250210IB
///
public class GuolianContractNoGenerator
{
private static readonly object _syncLock = new object();
///
/// 生成国贸交易确认书编号
///
/// 数据库上下文
/// 交易记录
/// 客户代码
/// 确认书编号
public static string Generate(
YLContext dbContext,
trade trade,
string clientCode)
{
// 根据trade的OpponentRole判断是否为对客交易(甲方=对客)。glms客户规定甲方=对客
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}-FICC-";
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;
}
// 通过当天同客户同类交易的 id 排序确定序号(幂等),每个客户独立递增
var tradeDateStart = tradeDate.Date;
var sameDayTradesCount = dbContext.trade
.Count(t => t.TradeDate >= tradeDateStart
&& t.TradeDate < tradeDateStart.AddDays(1)
&& t.ClientId == trade.ClientId
&& t.OpponentRole == trade.OpponentRole
&& t.id < trade.id);
var sequenceNo = sameDayTradesCount + 1;
if (isClientTrade)
{
return $"{prefix}{sequenceNo:D2}-{underlyingCode}";
}
else
{
return $"{prefix}{NumberToLetter(sequenceNo)}-{underlyingCode}";
}
}
}
///
/// 将数字转换为字母序列(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;
}
}
}