using Flurl.Http.Configuration; using Humanizer; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using NodaTime; using OASystem.API.OAMethodLib; using OASystem.API.OAMethodLib.DeepSeekAPI; using OASystem.API.OAMethodLib.DoubaoAPI; using OASystem.API.OAMethodLib.Hotmail; using OASystem.API.OAMethodLib.MicrosoftGraphMailbox; using OASystem.API.OAMethodLib.QiYeWeChatAPI; using OASystem.API.OAMethodLib.Quartz.Business; using OASystem.API.OAMethodLib.TokenHubAI; using OASystem.API.OAMethodLib.TokenHubAI.Models; using OASystem.Domain.AesEncryption; using OASystem.Domain.Entities.Customer; using OASystem.Domain.Entities.PersonnelModule; using OASystem.Domain.ViewModels.QiYeWeChat; using OASystem.RedisRepository; using System.IdentityModel.Tokens.Jwt; using System.Text.Json; using static OASystem.API.OAMethodLib.Hotmail.HotmailService; using JsonSerializer = System.Text.Json.JsonSerializer; namespace OASystem.API.Controllers { /// /// AI测试控制器 /// [Route("api/[controller]")] public class AITestController : ControllerBase { private readonly IDoubaoService _doubaoService; private readonly ILogger _logger; private readonly IConfiguration _config; private readonly IQiYeWeChatApiService _qiYeWeChatApiService; private readonly System.Net.Http.IHttpClientFactory _httpClientFactory; private readonly HotmailService _hotmailService; private readonly IMicrosoftGraphMailboxService _microsoftGraphMailboxService; private readonly IOptionsMonitor _microsoftGraphMailboxOptions; private readonly MessageRepository _message; private readonly SqlSugarClient _sqlSugar; private readonly ITokenHubService _tokenHubService; private readonly IDeepSeekService _deepSeekService; public AITestController( IDoubaoService doubaoService, ILogger logger, IQiYeWeChatApiService qiYeWeChatApiService, HotmailService hotmailService, System.Net.Http.IHttpClientFactory httpClientFactory, IConfiguration config, IMicrosoftGraphMailboxService microsoftGraphMailboxService, IOptionsMonitor microsoftGraphMailboxOptions, IDeepSeekService deepSeekService, MessageRepository message, SqlSugarClient sqlSugar, ITokenHubService tokenHubService ) { _doubaoService = doubaoService; _logger = logger; _qiYeWeChatApiService = qiYeWeChatApiService; _hotmailService = hotmailService; _httpClientFactory = httpClientFactory; _config = config; _microsoftGraphMailboxService = microsoftGraphMailboxService; _deepSeekService = deepSeekService; _microsoftGraphMailboxOptions = microsoftGraphMailboxOptions; _message = message; _sqlSugar = sqlSugar; _tokenHubService = tokenHubService; } #region 企业微信发送邮件测试 /// /// 企业微信发送邮件测试 /// [HttpPost("sendEmail")] public async Task> SendEmail([FromForm] IFormFile[] feils) { try { var req = new EmailRequestDto() { ToEmails = new List { "johnny.yang@pan-american-intl.com" }, CcEmails = new List { "Roy.lei@pan-american-intl.com" }, BccEmails = new List { "Roy.lei@pan-american-intl.com" }, Subject = "测试邮件 - 来自企业微信API", Body = "这是一封通过企业微信API发送的测试邮件,包含附件。", Files = feils }; var response = await _qiYeWeChatApiService.EmailSendAsync(req); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "调用企业微信邮件API失败。"); return StatusCode(500, new { Message = "调用企业微信邮件API失败,请检查配置或网络。", Detail = ex.Message }); } } #endregion #region 豆包 AI /// /// 豆包基础对话 /// [HttpPost("doubao-chat")] public async Task> DoubaoChat(string question, bool isThinking = false) { try { var messages = new List { new DouBaoChatMessage { Role = DouBaoRole.user, Content = question } }; var options = new CompleteChatOptions { ThinkingOptions = new thinkingOptions { IsThinking = isThinking } }; var response = await _doubaoService.CompleteChatAsync(messages, options); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "调用豆包API失败。"); return StatusCode(500, new { Message = "调用豆包API失败", Detail = ex.Message }); } } /// /// 豆包上传文件 /// [HttpPost("doubao-upload")] public async Task> DoubaoUpload(IFormFile file, string purpose = "user_data") { if (file == null || file.Length == 0) return BadRequest("请选择要上传的文件"); try { var stream = file.OpenReadStream(); var existsFileExpand = new List { "pdf", "docx" }; if (!existsFileExpand.Contains(file.FileName.Split('.').Last().ToLower())) { return BadRequest("请上传pdf、docx文件!不支持其他文件"); } if (file.FileName.Split('.').Last().ToLower() == "docx") { using var docxStream = file.OpenReadStream(); var pdfStream = DoubaoService.ConvertDocxStreamToPdfStream(docxStream); stream = pdfStream; } var response = await _doubaoService.UploadFileAsync(stream, file.FileName, purpose); stream.Dispose(); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "豆包上传文件失败"); return StatusCode(500, new { Message = "上传失败", Detail = ex.Message }); } } /// /// 豆包获取文件列表 /// [HttpGet("doubao-files")] public async Task> DoubaoListFiles() { try { var response = await _doubaoService.ListFilesAsync(); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "获取豆包文件列表失败"); return StatusCode(500, new { Message = "获取失败", Detail = ex.Message }); } } /// /// 豆包删除文件 /// [HttpDelete("doubao-file/{fileId}")] public async Task> DoubaoDeleteFile(string fileId) { try { var response = await _doubaoService.DeleteFileAsync(fileId); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "删除豆包文件失败"); return StatusCode(500, new { Message = "删除失败", Detail = ex.Message }); } } /// /// 豆包多模态对话(支持文本+图片) /// /// 表单请求参数 [HttpPost("doubao-multimodal-chat")] public async Task> DoubaoMultimodalChat([FromForm] DoubaoMultimodalChatRequest request) { if (string.IsNullOrWhiteSpace(request.Question)) return BadRequest("问题不能为空"); try { var contentItems = new List { new DoubaoMultimodalContentItem { Type = "text", Text = request.Question.Trim() } }; if (!string.IsNullOrWhiteSpace(request.FileId)) { contentItems.Add(new DoubaoMultimodalContentItem { Type = "file", FileId = request.FileId.Trim(), }); } if (request.Image != null && request.Image.Length > 0) { using var ms = new MemoryStream(); await request.Image.CopyToAsync(ms); var base64 = Convert.ToBase64String(ms.ToArray()); var mimeType = request.Image.ContentType ?? "image/jpeg"; var dataUrl = $"data:{mimeType};base64,{base64}"; contentItems.Add(new DoubaoMultimodalContentItem { Type = "image_url", ImageUrl = new DoubaoMultimodalImageUrl { Url = dataUrl } }); } var messages = new List { new DoubaoMultimodalChatMessage { Role = "user", Content = contentItems } }; var options = new CompleteMultimodalChatOptions { ThinkingOptions = new DoubaoMultimodalThinkingOptions { IsThinking = request.IsThinking, ReasoningEffort = "medium" } }; var response = await _doubaoService.CompleteMultimodalChatAsync(messages, options); return Ok(response ?? string.Empty); } catch (Exception ex) { _logger.LogError(ex, "调用豆包多模态API失败。"); return StatusCode(500, new { Message = "调用豆包多模态API失败", Detail = ex.Message }); } } #endregion #region 腾讯 TokenHub AI /// /// 简单对话 /// /// 对话请求 /// AI 回复 [HttpPost("simple")] public async Task SimpleChat([FromBody] ChatRequest request) { try { _logger.LogInformation("简单对话请求: {Message}", request.Messages); var reply = await _tokenHubService.ChatAsync(request); return Ok(new { success = true, data = reply, model = request.Model }); } catch (TokenHubException ex) { _logger.LogError(ex, "TokenHub 调用失败"); return StatusCode(ex.StatusCode, new JsonView() { Msg = ex.Message, Code = ex.StatusCode, Data = ex.ErrorDetail }); } catch (Exception ex) { _logger.LogError(ex, "对话失败"); return StatusCode(StatusCodes.Status400BadRequest, new JsonView() { Msg = ex.Message, Code = StatusCodes.Status400BadRequest, Data = ex.Message }); } } /// /// 带系统提示的对话 /// [HttpPost("system")] public async Task ChatWithSystem([FromBody] ChatRequest request) { try { var reply = await _tokenHubService.ChatAsync(request); return Ok(new { success = true, data = reply, model = request.Model ?? "hunyuan-2.0-instruct-20251111" }); } catch (TokenHubException ex) { return StatusCode(ex.StatusCode, new { success = false, error = ex.Message, code = ex.ErrorCode, requestId = ex.RequestId }); } catch (Exception ex) { _logger.LogError(ex, "对话失败"); return StatusCode(500, new { success = false, error = ex.Message }); } } /// /// 完整参数对话 /// [HttpPost("full")] public async Task FullChat([FromBody] ChatRequest request) { try { _logger.LogInformation("完整对话请求: Model={Model}, Messages={Count}", request.Model, request.Messages?.Count ?? 0); // 添加系统提示 if (!string.IsNullOrEmpty(request.SystemPrompt)) { request = request.WithSystemPrompt(request.SystemPrompt); } var response = await _tokenHubService.ChatAsync(request); if (!response.IsSuccess) { return BadRequest(new { success = false, error = response.ErrorMessage }); } return Ok(new { success = true, data = response.Content, model = response.Model, usage = response.Usage, finishReason = response.FinishReason, toolCalls = response.ToolCalls }); } catch (TokenHubException ex) { return StatusCode(ex.StatusCode, new { success = false, error = ex.Message, code = ex.ErrorCode, requestId = ex.RequestId }); } catch (Exception ex) { _logger.LogError(ex, "对话失败"); return StatusCode(500, new { success = false, error = ex.Message }); } } /// /// 流式对话(Server-Sent Events) /// [HttpPost("stream")] public async Task StreamChat([FromBody] ChatRequest request) { try { _logger.LogInformation("流式对话请求: {Message}", request.Messages.FirstOrDefault()); // 设置响应头 Response.ContentType = "text/event-stream; charset=utf-8"; Response.Headers.Append("Cache-Control", "no-cache"); Response.Headers.Append("Connection", "keep-alive"); Response.Headers.Append("X-Accel-Buffering", "no"); if (!string.IsNullOrEmpty(request.SystemPrompt)) { request = request.WithSystemPrompt(request.SystemPrompt); } var fullContent = new StringBuilder(); await _tokenHubService.StreamChatAsync( request, chunk => { if (chunk.IsDone) { // 发送结束标记 var endData = new { type = "done", fullContent = fullContent.ToString(), usage = chunk.Usage, model = chunk.Model }; Response.WriteAsync($"data: {JsonSerializer.Serialize(endData)}\n\n"); Response.Body.Flush(); return; } if (!string.IsNullOrEmpty(chunk.Content)) { fullContent.Append(chunk.Content); // 发送数据块 var chunkData = new { type = "chunk", content = chunk.Content, model = chunk.Model }; Response.WriteAsync($"data: {JsonSerializer.Serialize(chunkData)}\n\n"); Response.Body.Flush(); } if (chunk.ToolCalls != null && chunk.ToolCalls.Count > 0) { var toolData = new { type = "tool_call", tools = chunk.ToolCalls }; Response.WriteAsync($"data: {JsonSerializer.Serialize(toolData)}\n\n"); Response.Body.Flush(); } } ); } catch (Exception ex) { _logger.LogError(ex, "流式对话失败"); var errorData = new { type = "error", error = ex.Message }; await Response.WriteAsync($"data: {JsonSerializer.Serialize(errorData)}\n\n"); } } /// /// 带重试的对话 /// [HttpPost("retry")] public async Task ChatWithRetry([FromBody] ChatRequest request) { try { var response = await _tokenHubService.ChatWithRetryAsync(request); if (!response.IsSuccess) { return BadRequest(new { success = false, error = response.ErrorMessage }); } return Ok(new { success = true, data = response.Content, model = response.Model, usage = response.Usage }); } catch (TokenHubException ex) { return StatusCode(ex.StatusCode, new { success = false, error = ex.Message, code = ex.ErrorCode, requestId = ex.RequestId }); } catch (Exception ex) { _logger.LogError(ex, "对话失败"); return StatusCode(500, new { success = false, error = ex.Message }); } } /// /// 获取可用模型列表 /// [HttpGet("models")] public async Task GetModels() { try { var models = await _tokenHubService.GetAvailableModelsAsync(); return Ok(new { success = true, data = models, count = models.Count }); } catch (Exception ex) { _logger.LogError(ex, "获取模型列表失败"); return StatusCode(500, new { success = false, error = ex.Message }); } } /// /// 豆包多模态对话请求体(form-data) /// public class DoubaoMultimodalChatRequest { public string Question { get; set; } = string.Empty; public IFormFile? Image { get; set; } public bool IsThinking { get; set; } = false; public string FileId { get; set; } = string.Empty; } #endregion #region DeepSeek 测试 /// /// DeepSeek 带上下文的流式对话测试。响应为 NDJSON:每行一条 JSON,phase 为 reasoning、content 或 error。 /// system | user | assistant 角色。 /// [HttpPost("deepseek-chat-stream-with-history")] public async Task DeepSeekChatStreamWithHistory( [FromBody] DeepSeekChatStreamHistoryTestRequest request, CancellationToken cancellationToken = default) { if (request?.Messages == null || request.Messages.Count == 0) return BadRequest(new { message = "Messages 不能为空,且至少包含一条 user/system/assistant 消息。" }); Response.ContentType = "application/x-ndjson; charset=utf-8"; Response.Headers["Cache-Control"] = "no-cache"; static string NdjsonLine(DeepSeekStreamChunk c) => JsonConvert.SerializeObject(new { phase = c.Phase == DeepSeekStreamPhase.Reasoning ? "reasoning" : "content", text = c.Text }); try { await foreach (var chunk in _deepSeekService.ChatStreamWithHistoryAsync( request.Messages, string.IsNullOrWhiteSpace(request.Model) ? "deepseek-chat" : request.Model!.Trim(), request.Temperature, request.MaxTokens)) { cancellationToken.ThrowIfCancellationRequested(); await Response.WriteAsync(NdjsonLine(chunk) + "\n", cancellationToken); await Response.Body.FlushAsync(cancellationToken); } await Response.WriteAsync(JsonConvert.SerializeObject(new { phase = "success", text = "结束" }) + "\n", cancellationToken); await Response.Body.FlushAsync(cancellationToken); } catch (OperationCanceledException) { } catch (Exception ex) { _logger.LogError(ex, "DeepSeek 带历史流式对话失败"); await Response.WriteAsync( JsonConvert.SerializeObject(new { phase = "error", text = ex.Message }) + "\n", cancellationToken); } return new EmptyResult(); } /// /// DeepSeek 流式对话(含多轮)请求体 /// public class DeepSeekChatStreamHistoryTestRequest { public List Messages { get; set; } = new(); public string? Model { get; set; } = "deepseek-chat"; public float Temperature { get; set; } = 0.7f; public int MaxTokens { get; set; } = 4000; } #endregion /// /// hotmail 发送邮件 /// [HttpPost("hotmailSeed")] public async Task> HotmailSeed() { await _hotmailService.SendMailAsync( //"Roy.Lei.Atom@hotmail.com", "925554512@qq.com", //"johnny.yang@pan-american-intl.com", new HotmailService.MailDto() { Subject = "系统提醒", Content = "

这是一封Homail 发送的测试邮件

", //To = "Roy.lei@pan-american-intl.com" To = "johnny.yang@pan-american-intl.com" }); return StatusCode(200, new { Message = "操作成功。" }); } /// /// hotmail 发送邮件 /// [HttpPost("HotmailMerged")] public async Task> HotmailMerged() { // 1. 获取当前北京时间 (CST) var cstZone = CommonFun.GetCstZone(); var nowInCst = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, cstZone); // 2. 构造昨天的北京时间范围:00:00:00 到 23:59:59 var yesterdayStart = nowInCst.Date.AddDays(-1); // 昨天的 00:00:00 var yesterdayEnd = yesterdayStart.AddDays(1).AddTicks(-1); // 昨天的 23:59:59 var res = await _hotmailService.GetMergedMessagesAsync( new List() { "925554512@qq.com" }, yesterdayStart, yesterdayEnd ); return StatusCode(200, res); } /// /// hotmail 定时发送邮件 汇总 测试 /// [HttpPost("hotmailSummarySeedQW")] public async Task> HotmailSummary() { ProcessAndNotifySummary.ProcessAndNotifySummaryAsync(); return StatusCode(200, "发送成功"); } #region Microsoft Graph 邮箱测试(仅访问令牌) private const string GraphAccessTokenHeader = "X-Graph-Access-Token"; /// /// 优先级:请求头 X-Graph-Access-Token → 查询 graphAccessToken → bodyToken(发信)。 /// private string? ResolveGraphAccessToken(string? queryToken = null, string? bodyToken = null) { var header = Request.Headers[GraphAccessTokenHeader].FirstOrDefault(); if (!string.IsNullOrWhiteSpace(header)) return header.Trim(); if (!string.IsNullOrWhiteSpace(queryToken)) return queryToken.Trim(); if (!string.IsNullOrWhiteSpace(bodyToken)) return bodyToken.Trim(); return null; } /// /// 查询当前用户 GET /v1.0/me。必须提供 Graph 访问令牌。 /// [HttpGet("graph-mail/me")] public async Task GraphMailMe( [FromQuery] string? graphAccessToken = null, CancellationToken cancellationToken = default) { var bearer = ResolveGraphAccessToken(graphAccessToken); if (string.IsNullOrWhiteSpace(bearer)) return Unauthorized(new { message = "必须提供 Microsoft Graph 访问令牌:请求头 X-Graph-Access-Token 或查询参数 graphAccessToken" }); try { var json = await _microsoftGraphMailboxService.GetMeRawJsonAsync(bearer, cancellationToken); if (string.IsNullOrEmpty(json)) return StatusCode(502, new { message = "Graph 返回空正文" }); return Content(json, "application/json"); } catch (Exception ex) { _logger.LogError(ex, "Graph Mail /me 失败"); return StatusCode(500, new { message = ex.Message }); } } /// /// 查询收件箱。必须提供 Graph 访问令牌(需 Mail.Read)。默认 sinceUtc 为 UTC 近 24 小时。 /// /// 起始时间(UTC),ISO8601 /// 或使用请求头 X-Graph-Access-Token /// 取消标记 [HttpGet("graph-mail/inbox")] public async Task GraphMailInbox( [FromQuery] DateTime? sinceUtc = null, [FromQuery] string? graphAccessToken = null, CancellationToken cancellationToken = default) { var bearer = ResolveGraphAccessToken(graphAccessToken); if (string.IsNullOrWhiteSpace(bearer)) return Unauthorized(new { message = "必须提供 Microsoft Graph 访问令牌:请求头 X-Graph-Access-Token 或查询参数 graphAccessToken" }); var since = sinceUtc ?? DateTime.UtcNow.AddHours(-24); try { var json = await _microsoftGraphMailboxService.GetInboxMessagesJsonSinceAsync(since, bearer, cancellationToken); if (string.IsNullOrEmpty(json)) return StatusCode(502, new { message = "Graph 返回空正文" }); return Content(json, "application/json"); } catch (Exception ex) { _logger.LogError(ex, "Graph Mail inbox 失败"); return StatusCode(500, new { message = ex.Message }); } } /// /// Graph sendMail 纯文本。必须提供令牌(需 Mail.Send):头 / 查询 / Body.graphAccessToken。 /// [HttpPost("graph-mail/send")] public async Task GraphMailSend( [FromBody] GraphMailSendTestRequest request, [FromQuery] string? graphAccessToken = null, CancellationToken cancellationToken = default) { if (request == null || string.IsNullOrWhiteSpace(request.ToEmail)) return BadRequest(new { message = "ToEmail 不能为空" }); var bearer = ResolveGraphAccessToken(graphAccessToken, request.GraphAccessToken); if (string.IsNullOrWhiteSpace(bearer)) return Unauthorized(new { message = "必须提供 Microsoft Graph 访问令牌:X-Graph-Access-Token、?graphAccessToken 或 Body.graphAccessToken" }); var subject = string.IsNullOrWhiteSpace(request.Subject) ? $"OASystem Graph 测试邮件 {DateTime.Now:yyyy-MM-dd HH:mm:ss}" : request.Subject!; var body = request.Body ?? string.Empty; try { await _microsoftGraphMailboxService.SendMailAsync(request.ToEmail.Trim(), subject, body, bearer, cancellationToken); return Ok(new { ok = true, message = "sendMail 已提交", to = request.ToEmail.Trim(), subject }); } catch (HttpRequestException ex) { return StatusCode(502, new { message = "Graph HTTP 错误", detail = ex.Message }); } catch (Exception ex) { _logger.LogError(ex, "Graph Mail send 失败"); return StatusCode(500, new { message = ex.Message }); } } public class EmailAuthRedisCache { public string? AccessToken { get; set; } public string? HomeAccountId { get; set; } public string? UserTokenCacheBase64 { get; set; } public string? ClientId { get; set; } } /// /// 从 Redis 读取 MSAL 缓存与 HomeAccountId,静默刷新 Graph access_token。 /// [HttpGet("graph-mail/refresh-token")] public async Task RefreshAccessToken([FromQuery] string? redisKey = null) { var key = string.IsNullOrWhiteSpace(redisKey) ? "Email:AuthCache:345" : redisKey.Trim(); var redis = RedisFactory.CreateRedisRepository(); var json = await redis.StringGetRawAsync(key); if (string.IsNullOrWhiteSpace(json)) { return BadRequest(new { message = $"Redis 键 {key} 不存在或为空" }); } EmailAuthRedisCache? cacheEntry; try { cacheEntry = JsonConvert.DeserializeObject(json); } catch (System.Text.Json.JsonException ex) { _logger.LogWarning(ex, "Redis 键 {Key} 内容不是合法 JSON(应用 StringGetRawAsync + JSON,勿用 StringGetAsync,后者为 BinaryFormatter)", key); return BadRequest(new { message = "Redis 值为 JSON 文本时须用 StringGetRawAsync 再反序列化;StringGetAsync 仅适用于 BinaryFormatter 写入的数据", detail = ex.Message }); } if (cacheEntry == null || string.IsNullOrWhiteSpace(cacheEntry.UserTokenCacheBase64) || string.IsNullOrWhiteSpace(cacheEntry.HomeAccountId)) { return BadRequest(new { message = "JSON 中缺少 UserTokenCacheBase64 或 HomeAccountId" }); } var accessToken = await _microsoftGraphMailboxService.RefreshAccessTokenAsync( cacheEntry.ClientId, "common", new[] { "Mail.Read", "User.Read", "Mail.Send" }, cacheEntry.UserTokenCacheBase64, cacheEntry.HomeAccountId); return Ok(new { accessToken }); } /// /// Graph 发信测试请求体 /// public class GraphMailSendTestRequest { public string ToEmail { get; set; } = string.Empty; public string? Subject { get; set; } public string? Body { get; set; } /// Microsoft Graph 访问令牌(也可用请求头 X-Graph-Access-Token) public string? GraphAccessToken { get; set; } } #endregion #region 客户名单解密 /// /// 客户名单解密 /// [HttpPost("deleClientList")] public async Task> DeleClientList() { var data = await _message._sqlSugar.Queryable().Where(x => x.IsDel == 0 ) .LeftJoin((x,y) => x.CrmCompanyId == y.Id) .Select((x, y) => new { x.Id, x.FirstName, x.LastName, y.CompanyFullName, x.ClientPhone, x.Tel, x.Phone, x.Job, x.CreateTime }).ToListAsync(); var newData = new List(); foreach (var item in data) { newData.Add(new { item.Id, LastName = AesEncryptionHelper.Decrypt(item.LastName), FirstName = AesEncryptionHelper.Decrypt(item.FirstName), CompanyFullName = AesEncryptionHelper.Decrypt(item.CompanyFullName), Job = AesEncryptionHelper.Decrypt(item.Job), ClientPhone = AesEncryptionHelper.Decrypt(item.ClientPhone), Tel = AesEncryptionHelper.Decrypt(item.Tel), Phone = AesEncryptionHelper.Decrypt(item.Phone), item.CreateTime }); } return StatusCode(200, newData); } #endregion #region 工资计算测试 /// /// 工资计算测试 - 企微考勤信息 /// [HttpPost("SalaryCalculator")] public async Task> SalaryCalculator() { // 计算本月工资起止时间 比如是2月的1号-28号,那就是2月1号的零点到3月1号的零点 DateTime startDt = Convert.ToDateTime("2026-03-01"); DateTime endDt = Convert.ToDateTime("2026-03-31"); string thisYearMonth = "2026-03"; //本月工资年月 string preYearMonth = "2026-03"; //上月工资年月 // 检查是否存在 //var isExists = await _sqlSugar.Queryable().AnyAsync(it => it.YearMonth == thisYearMonth); //if (isExists) return Ok(JsonView(false, $"{thisYearMonth} 工资数据已存在。")); var preWageSheetItems = await _sqlSugar.Queryable() .Where(it => it.IsDel == 0 && it.YearMonth == preYearMonth) .ToListAsync(); if (!preWageSheetItems.Any()) return Ok(JsonView(false, "上月工资数据不存在。")); //处理上个月同月同人 多条数据 List preWageSheetItems1 = preWageSheetItems .GroupBy(it => new { it.YearMonth, it.UserId }) .Select(it => it.FirstOrDefault(item => item.Basic != 0)) .Where(it => it != null) .ToList()!; var res = await PayrollComputation_v1.SalaryCalculatorAsync(preWageSheetItems1,208, thisYearMonth, startDt, endDt); return Ok(res); } #endregion } }