Przeglądaj źródła

Merge branch 'develop' of http://132.232.92.186:3000/XinXiBu/OA2023 into develop

yuanrf 3 godzin temu
rodzic
commit
f56bfa7d40

+ 0 - 1
OASystem/OASystem.Api/Controllers/AuthController.cs

@@ -191,7 +191,6 @@ namespace OASystem.API.Controllers
             return Ok(JsonView(view));
         }
 
-
         /// <summary>
         /// 移动端用户登录
         /// </summary>

+ 624 - 4
OASystem/OASystem.Api/Controllers/FinancialController.cs

@@ -3,7 +3,6 @@ using Aspose.Words;
 using EyeSoft.Extensions;
 using EyeSoft.IO;
 using FluentValidation;
-using Humanizer;
 using NPOI.SS.UserModel;
 using NPOI.XSSF.UserModel;
 using OASystem.API.OAMethodLib;
@@ -911,7 +910,8 @@ namespace OASystem.API.Controllers
             return Ok(JsonView(true, "操作成功!", new List<dynamic> {
                 new { Id = 1, Name = "生成收款单(四川)" },
                 new { Id = 2, Name = "生成收款单(北京)" },
-                new { Id = 3, Name = "汇款账单" }
+                new { Id = 3, Name = "汇款账单" },
+                new { Id = 4, Name = "实际报价明细" }
             }
             ));
         }
@@ -1491,6 +1491,117 @@ namespace OASystem.API.Controllers
 
                     return Ok(JsonView(true, "成功", new { Url = url }));
                 }
+                else if (dto.FileType == 4) //实际报价明细
+                {
+                    try
+                    {
+                        // 1. 文件验证
+                        if (_DelegationInfo.FrFilePaths == null || _DelegationInfo.FrFilePaths.Count < 1)
+                        {
+                            return Ok(JsonView(false, "该团组未上传实际报价相关文件!"));
+                        }
+
+                        // 2. 构建源文件夹路径
+                        var groupDir = @$"{_DelegationInfo.TeamName}({_DelegationInfo.Id})";
+                        var sourceFolderPath = AppSettingsHelper.Get("ReceivablesUploadFileBasePath") + @"/" + groupDir;
+
+                        if (!Directory.Exists(sourceFolderPath))
+                        {
+                            return NotFound(JsonView(false, $"文件夹不存在: {sourceFolderPath}"));
+                        }
+
+                        // 3. 检查文件夹是否为空
+                        var files = Directory.GetFiles(sourceFolderPath, "*.*", SearchOption.AllDirectories);
+                        if (files.Length == 0)
+                        {
+                            return BadRequest(JsonView(false, $"文件夹为空: {Path.GetFileName(sourceFolderPath)}"));
+                        }
+
+                        // 4. 生成目标Zip文件路径
+                        string targetZipPath = sourceFolderPath.Trim();
+                        
+                        // 5. 确保目标目录存在
+                        var targetDirectory = Path.GetDirectoryName(targetZipPath);
+                        if (!Directory.Exists(targetDirectory))
+                        {
+                            Directory.CreateDirectory(targetDirectory);
+                        }
+
+                        // 6. 检查目标文件是否已存在(可选:覆盖或返回错误)
+                        if (System.IO.File.Exists(targetZipPath))
+                        {
+                            System.IO.File.Delete(targetZipPath);
+                        }
+
+                        //// 7. 创建Zip包
+                        //using (var zipFileStream = new FileStream(targetZipPath, FileMode.Create))
+                        //{
+                        //    using (var zipArchive = new ZipArchive(zipFileStream, ZipArchiveMode.Create))
+                        //    {
+                        //        foreach (string filePath in filePaths)
+                        //        {
+                        //            // 增加空值/空字符串校验,提升代码健壮性
+                        //            if (!string.IsNullOrEmpty(filePath) && System.IO.File.Exists(filePath))
+                        //            {
+                        //                // 获取文件名
+                        //                string fileName = Path.GetFileName(filePath);
+                        //                // 将文件添加到ZIP归档中
+                        //                zipArchive.CreateEntryFromFile(filePath, fileName);
+                        //            }
+                        //        }
+                        //    }
+                        //}
+
+
+                        //// 8. 验证Zip文件是否创建成功
+                        //if (!File.Exists(targetZipPath))
+                        //{
+                        //    return StatusCode(StatusCodes.Status500InternalServerError,
+                        //        JsonView(false, "Zip文件创建失败"));
+                        //}
+
+                        //var fileInfo = new FileInfo(targetZipPath);
+                        //if (fileInfo.Length == 0)
+                        //{
+                        //    SafeDeleteFile(targetZipPath);
+                        //    return StatusCode(StatusCodes.Status500InternalServerError,
+                        //        JsonView(false, "创建的Zip文件为空"));
+                        //}
+
+                        //// 9. 计算文件哈希(可选)
+                        //var fileHash = await CalculateFileHashAsync(targetZipPath);
+
+                        //// 10. 创建返回结果
+                        //var result = new ZipCreationResult
+                        //{
+                        //    Success = true,
+                        //    Message = "文件夹打包成功",
+                        //    SourceFolderPath = sourceFolderPath,
+                        //    ZipFilePath = targetZipPath,
+                        //    ZipFileSize = fileInfo.Length,
+                        //    ZipFileSizeFormatted = FormatFileSize(fileInfo.Length),
+                        //    FileCount = creationResult.FileCount,
+                        //    ZipCreationTime = fileInfo.CreationTime,
+                        //    FileHash = fileHash,
+                        //    AccessUrl = GenerateAccessUrl(targetZipPath) // 生成访问URL
+                        //};
+
+                        //// 11. 记录到数据库(可选)
+                        //await SaveZipCreationRecordAsync(request, result);
+
+                        //_logger.LogInformation("文件夹打包成功: {SourceFolderPath} -> {TargetZipPath}, 大小: {Size}, 文件数: {FileCount}",
+                        //    sourceFolderPath, targetZipPath, result.ZipFileSizeFormatted, result.FileCount);
+
+                        return Ok(JsonView(false, "操作失败!"));
+                    }
+                    catch (Exception ex)
+                    {
+                        //_logger.LogError(ex, "文件夹打包失败: {SourceFolderPath}", request?.SourceFolderPath);
+                        //return StatusCode(StatusCodes.Status500InternalServerError,
+                        //    JsonView(false, $"文件夹打包失败: {ex.Message}"));
+                    }
+
+                }
 
                 return Ok(JsonView(false, "操作失败!"));
             }
@@ -1562,7 +1673,6 @@ namespace OASystem.API.Controllers
             lshCell.AppendChild(p);
         }
 
-
         /// <summary>
         /// 已收账单 
         /// 提示导入出入境报价费用
@@ -1579,6 +1689,516 @@ namespace OASystem.API.Controllers
             var view = _mapper.Map<List<ProceedsReceivedNewView>>(data);
             return Ok(JsonView(true, "操作成功", view));
         }
+
+
+        /// <summary>
+        /// 已收账单上传文件
+        /// </summary>
+        /// <param name="groupId">团组ID</param>
+        /// <param name="files">上传的文件列表</param>
+        /// <returns>操作结果</returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status400BadRequest)]
+        public async Task<IActionResult> ReceivablesUploadFile(int groupId, List<IFormFile> files)
+        {
+            try
+            {
+                // 1. 参数验证
+                if (groupId < 1) return Ok(JsonView(false, "请传入有效的GroupId参数!"));
+
+                if (files == null || !files.Any()) return Ok(JsonView(false, "请选择要上传的文件!"));
+
+                // 2. 文件验证
+                var fileValidationResult = await ValidateFilesAsync(files);
+                if (!fileValidationResult.IsValid)
+                {
+                    return Ok(JsonView(false, fileValidationResult.ErrorMessage));
+                }
+
+                // 3. 检查团组信息
+                var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().FirstAsync(x => x.IsDel == 0 && x.Id == groupId);
+                if (groupInfo == null)
+                {
+                    return Ok(JsonView(false, $"团组信息不存在!"));
+                }
+
+                // 4. 创建文件存储目录
+                var groupDir = @$"{groupInfo.TeamName}({groupInfo.Id})";
+                var fileServerPath = AppSettingsHelper.Get("ReceivablesUploadFileBasePath") + @"/" + groupDir;
+                if (!Directory.Exists(fileServerPath)) Directory.CreateDirectory(fileServerPath);
+
+                // 5. 为文件生成安全文件名
+                var uploadResults = new List<string>();
+                foreach (var file in fileValidationResult.ValidFiles)
+                {
+                    var safeFileName = GenerateSafeFileName(file.FileName, groupInfo);
+                    var savePath = Path.Combine(fileServerPath, safeFileName);
+
+                    // 防止文件名冲突
+                    var uniqueFileName = GetUniqueFileName(savePath);
+                    var finalSavePath = Path.Combine(fileServerPath, uniqueFileName);
+
+                    // 确保资源释放
+                    using (var fileStream = new FileStream(finalSavePath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
+                    {
+
+                        await file.CopyToAsync(fileStream);
+
+                        // 刷新缓冲区
+                        await fileStream.FlushAsync();
+                    }
+
+                    uploadResults.Add($"{groupDir}/{uniqueFileName}");
+
+                }
+
+                if (!uploadResults.Any())
+                {
+                    return Ok(JsonView(false, "文件存储失败!"));
+                }
+
+                // 5. 数据库存储路径
+                var upd = await _sqlSugar.Updateable<Grp_DelegationInfo>()
+                    .SetColumns(x => x.FrFilePaths == uploadResults)
+                    .Where(x => x.Id == groupId)
+                    .ExecuteCommandAsync();
+                if (upd < 1)
+                {
+                    return Ok(JsonView(false, "文件路径存储失败!"));
+                }
+
+                // 返回上传结果
+                var url = AppSettingsHelper.Get("OfficeBaseUrl");
+                var pathPrefix = AppSettingsHelper.Get("ReceivablesUploadFileFtpPath");
+                for (int i = 0; i < uploadResults.Count; i++)
+                {
+                    uploadResults[i] = @$"{url}/{pathPrefix}/{uploadResults[i]}";
+                }
+
+                return Ok(JsonView(true, "操作成功!", uploadResults));
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "已收账单文件上传失败,GroupId: {GroupId}", groupId);
+                return StatusCode(StatusCodes.Status500InternalServerError,
+                    JsonView(false, "系统繁忙,请稍后重试"));
+            }
+        }
+
+        #region 文件名安全处理
+
+        /// <summary>
+        /// 生成安全的文件名
+        /// </summary>
+        private string GenerateSafeFileName(string originalFileName, Grp_DelegationInfo groupInfo)
+        {
+            // 1. 移除路径信息,只保留文件名
+            var fileName = Path.GetFileName(originalFileName);
+
+            // 2. 移除特殊字符和危险字符
+            fileName = RemoveDangerousCharacters(fileName);
+
+            // 3. 移除或替换空白字符
+            fileName = RemoveWhitespaceCharacters(fileName);
+
+            // 4. 限制文件名长度
+            fileName = LimitFileNameLength(fileName);
+
+            // 5. 可选:添加时间戳和团组标识
+            //fileName = AddFileIdentifier(fileName, groupInfo);
+
+            return fileName;
+        }
+
+        /// <summary>
+        /// 移除危险字符
+        /// </summary>
+        private string RemoveDangerousCharacters(string fileName)
+        {
+            if (string.IsNullOrWhiteSpace(fileName))
+                return Guid.NewGuid().ToString(); // 如果文件名无效,生成GUID
+
+            // 定义危险字符(包含路径遍历、特殊系统字符等)
+            var dangerousChars = new[]
+            {
+        "..", "/", "\\", ":", "*", "?", "\"", "<", ">", "|",
+        "\0", "\r", "\n", "\t", "\b", "\f"  // 控制字符
+    };
+
+            // 定义需要替换的字符
+            var charMap = new Dictionary<char, char>
+            {
+                ['#'] = '-',
+                ['%'] = '-',
+                ['&'] = '-',
+                ['@'] = '-',
+                ['+'] = '-',
+                ['='] = '-',
+                [';'] = '-',
+                [','] = '-',
+                ['`'] = '-',
+                ['~'] = '-',
+                ['!'] = '-',
+                ['^'] = '-',
+                ['('] = '-',
+                [')'] = '-',
+                ['['] = '-',
+                [']'] = '-',
+                ['{'] = '-',
+                ['}'] = '-',
+                ['$'] = '-'
+            };
+
+            // 移除危险字符串
+            foreach (var dangerous in dangerousChars)
+            {
+                fileName = fileName.Replace(dangerous, string.Empty);
+            }
+
+            // 替换特殊字符
+            var chars = fileName.ToCharArray();
+            for (int i = 0; i < chars.Length; i++)
+            {
+                if (charMap.TryGetValue(chars[i], out var replacement))
+                {
+                    chars[i] = replacement;
+                }
+            }
+
+            return new string(chars);
+        }
+
+        /// <summary>
+        /// 移除空白字符
+        /// </summary>
+        private string RemoveWhitespaceCharacters(string fileName)
+        {
+            // 移除开头和结尾的空白
+            fileName = fileName.Trim();
+
+            // 替换内部连续空白为单个下划线
+            var regex = new Regex(@"\s+");
+            fileName = regex.Replace(fileName, "_");
+
+            return fileName;
+        }
+
+        /// <summary>
+        /// 限制文件名长度
+        /// </summary>
+        private string LimitFileNameLength(string fileName)
+        {
+            const int maxFileNameLength = 100; // 限制文件名最大长度
+
+            if (fileName.Length <= maxFileNameLength)
+                return fileName;
+
+            // 获取文件扩展名
+            var extension = Path.GetExtension(fileName);
+            var nameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
+
+            if (string.IsNullOrEmpty(extension))
+            {
+                // 没有扩展名,直接截断
+                return fileName.Substring(0, maxFileNameLength);
+            }
+
+            // 保留扩展名,截断主文件名
+            var maxNameLength = maxFileNameLength - extension.Length;
+            if (maxNameLength <= 0)
+            {
+                // 如果扩展名太长,使用短名称
+                return Guid.NewGuid().ToString().Substring(0, 8) + extension;
+            }
+
+            if (nameWithoutExtension.Length > maxNameLength)
+            {
+                nameWithoutExtension = nameWithoutExtension.Substring(0, maxNameLength);
+            }
+
+            return nameWithoutExtension + extension;
+        }
+
+        /// <summary>
+        /// 添加文件标识符
+        /// </summary>
+        private string AddFileIdentifier(string fileName, Grp_DelegationInfo groupInfo)
+        {
+            // 可选:添加时间戳
+            var timestamp = DateTime.Now.ToString("yyyyMMddHHmmss");
+
+            // 可选:添加团组标识
+            var groupIdentifier = $"{groupInfo.TeamName}_{groupInfo.Id}";
+            groupIdentifier = RemoveDangerousCharacters(groupIdentifier);
+            groupIdentifier = RemoveWhitespaceCharacters(groupIdentifier);
+
+            // 获取文件扩展名
+            var extension = Path.GetExtension(fileName);
+            var nameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
+
+            // 如果文件名太短,添加标识符
+            if (string.IsNullOrWhiteSpace(nameWithoutExtension) || nameWithoutExtension.Length < 3)
+            {
+                nameWithoutExtension = $"{groupIdentifier}_{timestamp}";
+            }
+            else
+            {
+                // 添加标识符前缀
+                nameWithoutExtension = $"{groupIdentifier}_{timestamp}_{nameWithoutExtension}";
+            }
+
+            return nameWithoutExtension + extension;
+        }
+
+        /// <summary>
+        /// 获取唯一文件名(防止冲突)
+        /// </summary>
+        private string GetUniqueFileName(string filePath)
+        {
+            var directory = Path.GetDirectoryName(filePath);
+            var fileName = Path.GetFileName(filePath);
+            var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
+            var extension = Path.GetExtension(fileName);
+
+            var counter = 1;
+            var newFileName = fileName;
+
+            while (System.IO.File.Exists(Path.Combine(directory, newFileName)))
+            {
+                newFileName = $"{fileNameWithoutExtension}_{counter}{extension}";
+                counter++;
+
+                // 防止无限循环
+                if (counter > 1000)
+                {
+                    newFileName = $"{fileNameWithoutExtension}_{Guid.NewGuid().ToString().Substring(0, 8)}{extension}";
+                    break;
+                }
+            }
+
+            return newFileName;
+        }
+
+        #region 私有方法
+
+        /// <summary>
+        /// 验证文件
+        /// </summary>
+        private async Task<(bool IsValid, string ErrorMessage, List<IFormFile> ValidFiles)> ValidateFilesAsync(List<IFormFile> files)
+        {
+            var allowedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+            {
+                ".pdf", ".xls", ".xlsx", ".jpg", ".jpeg", ".png", ".doc", ".docx", ".txt"
+            };
+
+            const long maxFileSize = 20 * 1024 * 1024; // 20MB
+
+            var invalidFiles = new List<string>();
+            var validFiles = new List<IFormFile>();
+
+            foreach (var file in files)
+            {
+                // 检查文件大小
+                if (file.Length > maxFileSize)
+                {
+                    string[] sizes = { "B", "KB", "MB", "GB", "TB" };
+                    double len = file.Length;
+                    int order = 0;
+
+                    while (len >= 1024 && order < sizes.Length - 1)
+                    {
+                        order++;
+                        len = len / 1024;
+                    }
+
+                    string fileSize = string.Format("{0:0.##} {1}", len, sizes[order]);
+
+                    invalidFiles.Add($"{file.FileName} 文件大小({fileSize})超过限制(20MB)");
+                    continue;
+                }
+
+                // 检查扩展名
+                var fileExtension = Path.GetExtension(file.FileName);
+                if (string.IsNullOrEmpty(fileExtension) || !allowedExtensions.Contains(fileExtension))
+                {
+                    invalidFiles.Add($"{file.FileName} 文件类型({fileExtension})不支持");
+                    continue;
+                }
+
+                // 基础文件名安全检查
+                var fileName = Path.GetFileName(file.FileName);
+                if (!IsFileNameSafe(fileName))
+                {
+                    invalidFiles.Add($"{file.FileName} 文件名包含非法字符");
+                    continue;
+                }
+
+                validFiles.Add(file);
+            }
+
+            if (invalidFiles.Any())
+            {
+                return (false, $"文件验证失败: {string.Join("; ", invalidFiles)}", new List<IFormFile>());
+            }
+
+            return (true, string.Empty, validFiles);
+        }
+
+        /// <summary>
+        /// 基础文件名安全检查
+        /// </summary>
+        private bool IsFileNameSafe(string fileName)
+        {
+            if (string.IsNullOrWhiteSpace(fileName))
+                return false;
+
+            // 检查是否包含路径遍历
+            if (fileName.Contains(".."))
+                return false;
+
+            // 检查是否包含路径分隔符
+            if (fileName.Contains("/") || fileName.Contains("\\"))
+                return false;
+
+            // 检查系统保留字符
+            var systemChars = new[] { ":", "*", "?", "\"", "<", ">", "|", "\0" };
+            if (systemChars.Any(ch => fileName.Contains(ch)))
+                return false;
+
+            return true;
+        }
+
+        // ... 其他方法保持不变
+
+        #endregion
+
+        #endregion
+
+        /// <summary>
+        /// 已收账单 删除已上传的文件
+        /// </summary>
+        /// <param name="groupId">团组ID</param>
+        /// <param name="fileName">要删除的文件名</param>
+        /// <returns>操作结果</returns>
+        [HttpDelete]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status400BadRequest)]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status404NotFound)]
+        public async Task<IActionResult> DeleteReceivablesFile(int groupId, [FromBody] string fileName)
+        {
+            try
+            {
+                // 1. 参数验证
+                if (groupId < 1)
+                {
+                    return Ok(JsonView(false, "请传入有效的GroupId参数!"));
+                }
+
+                if (string.IsNullOrWhiteSpace(fileName))
+                {
+                    return Ok(JsonView(false, "请指定要删除的文件名!"));
+                }
+
+                // 2. 安全检查:防止路径遍历攻击
+                if (fileName.Contains("..") || fileName.Contains("/") || fileName.Contains("\\"))
+                {
+                    return Ok(JsonView(false, "文件名包含非法字符!"));
+                }
+
+                // 3. 获取团组信息
+                var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().FirstAsync(x => x.IsDel == 0 && x.Id == groupId);
+                if (groupInfo == null)
+                {
+                    return Ok(JsonView(false, $"团组信息不存在!"));
+                }
+
+
+                // 4. 获取当前文件列表
+                var currentFilePaths = groupInfo.FrFilePaths ?? new List<string>();
+                var groupDir = @$"{groupInfo.TeamName}({groupInfo.Id})";
+
+                // 5. 检查要删除的文件是否存在
+                if (!currentFilePaths.Contains(groupDir + @"/" + fileName))
+                {
+                    return Ok(JsonView(false, $"文件 '{fileName}' 不存在于该团组的文件列表中!"));
+                }
+
+                // 6. 构建文件完整路径
+                var fileServerPath = AppSettingsHelper.Get("ReceivablesUploadFileBasePath") + @"/" + groupDir;
+                var fullFilePath = Path.Combine(fileServerPath, fileName);
+
+                // 7. 验证文件是否存在
+                if (!System.IO.File.Exists(fullFilePath))
+                {
+                    // 文件在磁盘上不存在,但从数据库中移除记录
+                    var upd = await RemoveFileFromDatabaseAsync(groupId, groupDir + @"/" + fileName, currentFilePaths);
+                    return Ok(JsonView(false, $"文件 '{fileName}' 在服务器上不存在,已从数据库中移除记录!"));
+                }
+
+                // 8. 删除物理文件
+                bool fileDeleted = false;
+                try
+                {
+                    System.IO.File.Delete(fullFilePath);
+                    fileDeleted = true;
+                }
+                catch (IOException ioEx)
+                {
+                    _logger.LogError(ioEx, "删除文件失败,FilePath: {FilePath}", fullFilePath);
+                    return Ok(JsonView(false, $"删除文件失败:文件可能正在被使用或不存在。"));
+                }
+                catch (UnauthorizedAccessException authEx)
+                {
+                    _logger.LogError(authEx, "删除文件权限不足,FilePath: {FilePath}", fullFilePath);
+                    return Ok(JsonView(false, $"删除文件失败:权限不足。"));
+                }
+
+                if (!fileDeleted)
+                {
+                    return Ok(JsonView(false, $"删除文件失败!"));
+                }
+
+                // 9. 更新数据库(从文件列表中移除该文件)
+                await RemoveFileFromDatabaseAsync(groupId, groupDir + @"/" + fileName, currentFilePaths);
+
+                return Ok(JsonView(true, $"文件 '{fileName}' 删除成功!"));
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "删除文件失败,GroupId: {GroupId}, FileName: {FileName}", groupId, fileName);
+                return StatusCode(StatusCodes.Status500InternalServerError,
+                    JsonView(false, "系统繁忙,请稍后重试"));
+            }
+        }
+
+        /// <summary>
+        /// 从数据库中移除文件记录
+        /// </summary>
+        private async Task<bool> RemoveFileFromDatabaseAsync(int groupId, string fileName, List<string> currentFilePaths)
+        {
+            try
+            {
+                // 从当前文件列表中移除指定文件
+                var updatedFileList = currentFilePaths.Where(f => f != fileName).ToList();
+
+                // 更新数据库
+                var upd = await _sqlSugar.Updateable<Grp_DelegationInfo>()
+                    .SetColumns(x => x.FrFilePaths == updatedFileList)
+                    .Where(x => x.Id == groupId)
+                    .ExecuteCommandAsync();
+                return upd > 0;
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "更新数据库文件列表失败,GroupId: {GroupId}, FileName: {FileName}",
+                    groupId, fileName);
+
+            }
+
+            return false;
+        }
+
         #endregion
 
         #region 已收款项
@@ -7234,7 +7854,7 @@ Group by PriceType ", dto.diId);
                 foreach (var keyword in keywords.Where(k => !string.IsNullOrWhiteSpace(k)))
                 {
                     query = query.Where(x => x.TeamName.Contains(keyword.Trim()));
-                    
+
                 }
             }
 

+ 7 - 4
OASystem/OASystem.Api/Controllers/GroupsController.cs

@@ -1716,6 +1716,7 @@ namespace OASystem.API.Controllers
         /// <param name="dto"></param>
         /// <returns></returns>
         [HttpPost]
+        [ApiLog("Grp_DelegationInfo", OperationEnum.Edit)]
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
         public async Task<IActionResult> GroupOperation(GroupOperationDto dto)
         {
@@ -1903,6 +1904,7 @@ namespace OASystem.API.Controllers
         /// <param name="dto"></param>
         /// <returns></returns>
         [HttpPost]
+        [ApiLog("Grp_DelegationInfo", OperationEnum.Edit)]
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
         public async Task<IActionResult> GroupProcessOperation(GroupProcessOperationDto dto)
         {
@@ -2105,6 +2107,7 @@ namespace OASystem.API.Controllers
         /// <param name="dto"></param>
         /// <returns></returns>
         [HttpPost]
+        [ApiLog("Grp_DelegationInfo", OperationEnum.Edit)]
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
         public async Task<IActionResult> GroupProcessOperation1(GroupProcessOperationDto dto)
         {
@@ -2303,6 +2306,7 @@ namespace OASystem.API.Controllers
         /// <param name="dto"></param>
         /// <returns></returns>
         [HttpPost]
+        [ApiLog("Grp_DelegationInfo", OperationEnum.Del)]
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
         public async Task<IActionResult> GroupDel(GroupDelDto dto)
         {
@@ -6011,7 +6015,6 @@ FROM
                     return Ok(JsonView(false, "正在开发中,请等待。。。"));
             }
 
-            //压缩文件
             // 压缩文件
             var zipFilePathName = $"{groupInfo.TeamName}({visaTypeName}).zip";
             var zipFilePath = $"{fileServerPath}{zipFilePathName}";
@@ -11910,7 +11913,7 @@ FROM
                         }
                         else dic.Add("SubZS", "0.00");
                         //删除多余行
-                        while (table1.Rows.Count > table1Row)
+                        while (table1!=null && table1.Rows.Count > table1Row)
                         {
                             table1.Rows.RemoveAt(table1Row);
                         }
@@ -11960,7 +11963,7 @@ FROM
                         else dic.Add("SubHS", "0.00");
 
                         //删除多余行
-                        while (table2.Rows.Count > table2Row)
+                        while (table2 != null && table2.Rows.Count > table2Row)
                         {
                             table2.Rows.RemoveAt(table2Row);
                         }
@@ -12014,7 +12017,7 @@ FROM
                         else dic.Add("SubGZF", "0.00");
 
                         //删除多余行
-                        while (table3.Rows.Count > table3Row)
+                        while (table3 != null && table3.Rows.Count > table3Row)
                         {
                             table3.Rows.RemoveAt(table3Row);
                         }

+ 4 - 4
OASystem/OASystem.Api/Controllers/StatisticsController.cs

@@ -1103,7 +1103,7 @@ ORDER BY
                 }
 
                 _geView.GroupVisaFeeViews = groupVisaFeeViews;
-                _geView.GroupVisaFeeStr = string.Format(@"人民币总费用:{0} CNY", VisaCNYTotalPirce.ConvertToDecimal1().ToString("#.00"));
+                _geView.GroupVisaFeeStr = string.Format(@"人民币总费用:{0} CNY", VisaCNYTotalPirce.ConvertToDecimal1().ToString("#0.00"));
                 #endregion
 
                 #region 邀请/公务活动 81 isAuditSql
@@ -1211,7 +1211,7 @@ ORDER BY
                 }
 
                 _geView.GroupInvitationalFeeViews = groupInvitationalFeeViews;
-                _geView.GroupInvitationalFeeStr = string.Format(@"人民币总费用:{0} CNY", InvitationalCNYTotalPrice.ToString("#.00"));
+                _geView.GroupInvitationalFeeStr = string.Format(@"人民币总费用:{0} CNY", InvitationalCNYTotalPrice.ToString("#0.00"));
                 #endregion
 
                 #region 保险费用 82 isAuditSql
@@ -3285,7 +3285,7 @@ ORDER BY
                 }
 
                 _geView.GroupVisaFeeViews = groupVisaFeeViews;
-                _geView.GroupVisaFeeStr = string.Format(@"人民币总费用:{0} CNY", VisaCNYTotalPirce.ConvertToDecimal1().ToString("#.00"));
+                _geView.GroupVisaFeeStr = string.Format(@"人民币总费用:{0} CNY", VisaCNYTotalPirce.ConvertToDecimal1().ToString("#0.00"));
                 #endregion
 
                 #region 邀请/公务活动  CTable = 81
@@ -3392,7 +3392,7 @@ ORDER BY
                 }
 
                 _geView.GroupInvitationalFeeViews = groupInvitationalFeeViews;
-                _geView.GroupInvitationalFeeStr = string.Format(@"人民币总费用:{0} CNY", InvitationalCNYTotalPrice.ToString("#.00"));
+                _geView.GroupInvitationalFeeStr = string.Format(@"人民币总费用:{0} CNY", InvitationalCNYTotalPrice.ToString("#0.00"));
                 #endregion
 
                 #region 保险费用

+ 1 - 2
OASystem/OASystem.Api/Middlewares/RecordAPIOperationMiddleware.cs

@@ -1,8 +1,6 @@
 using OASystem.Domain.AesEncryption;
 using OASystem.Domain.Attributes;
 using OASystem.Domain.Entities.Customer;
-using Org.BouncyCastle.Asn1.Pkcs;
-using System.Net;
 using UAParser;
 using static OASystem.API.OAMethodLib.JWTHelper;
 
@@ -50,6 +48,7 @@ namespace OASystem.API.Middlewares
                 int portType = 1, userId = 0, id = 0, status = 0;
                 string updatePreData = string.Empty, updateBefData = string.Empty;
                 bool param5Bool = false, param6Bool = false;
+                
                 try
                 {
                     userId = await ReadToken(context);

+ 0 - 3
OASystem/OASystem.Api/Program.cs

@@ -185,7 +185,6 @@ builder.Services.AddScoped(options =>
             ConnectionString = _config.GetConnectionString("OA2023DB"),
             DbType = DbType.SqlServer,
             IsAutoCloseConnection = true,
-
         },
         new()
         {
@@ -202,8 +201,6 @@ builder.Services.AddScoped(options =>
             // 超过1秒
             if (db.Ado.SqlExecutionTime.TotalSeconds > 1)
             {
-                var fileName = db.Ado.SqlStackTrace.FirstFileName;
-                var fileLine = db.Ado.SqlStackTrace.FirstLine;
                 var FirstMethodName = db.Ado.SqlStackTrace.FirstMethodName;
                 //执行完了可以输出SQL执行时间 (OnLogExecutedDelegate) 
                 Console.WriteLine("NowTime:" + DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"));

+ 6 - 2
OASystem/OASystem.Api/appsettings.json

@@ -167,6 +167,10 @@
   "WageSheetExcelFptPath": "D:/FTP/File/OA2023/Office/WageSheetFile/",
   "WageSheetTaxExcelBaseUrl": "http://132.232.92.186:24/",
   "WageSheetTaxExcelFptPath": "D:/FTP/File/OA2023/Office/Excel/WageSheetTaxFile/",
+  //收款账单 - 文件路径配置
+  "ReceivablesUploadFileBasePath": "D:/FTP/File/OA2023/Office/Word/ForeignReceivables/UploadFile",
+  "ReceivablesUploadFileFtpPath": "Office/Word/ForeignReceivables/UploadFile",
+
   "CTableCorrelationPageDatas": [
     {
       "CTableId": 76, //CtableId 酒店预订
@@ -522,14 +526,14 @@
   //限流配置
   "RateLimiting": {
     "Enabled": true,
-    "DefaultLimit": 10,
+    "DefaultLimit": 20,
     "DefaultPeriod": 1,
     "Endpoints": [
       // 所有api接口统一限制:5次/秒
       {
         "Path": "/api/*",
         "Method": "*",
-        "Limit": 10,
+        "Limit": 20,
         "Period": 1,
         "Policy": "IP" //// 0=IP, 1=User, 2=Global, 3=Client
       }

+ 0 - 1
OASystem/OASystem.Domain/Entities/Financial/Fin_ForeignReceivables.cs

@@ -94,7 +94,6 @@ namespace OASystem.Domain.Entities.Financial
         [SugarColumn(IsNullable = true, ColumnDataType = "int")]
         public int AddingWay { get; set; }
 
-
         /// <summary>
         /// 审核状态 AddingWay==2该项有值
         /// 0 - 未审核 1 - 审核通过 2 - 审核不通过

+ 7 - 1
OASystem/OASystem.Domain/Entities/Groups/Grp_DelegationInfo.cs

@@ -265,6 +265,12 @@ namespace OASystem.Domain.Entities.Groups
         /// </summary>
         [SugarColumn(IsNullable = true, ColumnDataType = "datetime")]
         public DateTime? StepOperationTime { get; set; }
-    }
 
+        /// <summary>
+        /// 收款账单 - 存储上传文件路径集合
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)",IsJson = true)]
+        public List<string> FrFilePaths { get; set; }
+
+    }
 }

+ 14 - 14
OASystem/OASystem.Infrastructure/Repositories/Financial/ForeignReceivablesRepository.cs

@@ -391,20 +391,20 @@ namespace OASystem.Infrastructure.Repositories.Financial
             JsonView result = new() { Code = 400, Msg = "" };
 
             //var groupInfoData = await _delegationRep.GetGroupInfo(new GroupInfoDto() { Id = dto.DiId });
-            var groupInfoData = await _delegationRep.Query(x => x.Id == dto.DiId)
-                .Select(x => new
-                {
-                    x.Id,
-                    x.TeamName,
-                    x.TourCode,
-                    x.ClientName,
-                    VisitCountry = x.VisitCountry.Replace("|","、"),
-                    x.VisitDays,
-                    x.VisitPNumber,
-                    x.VisitStartDate,
-                    x.VisitEndDate
-                })
-                .FirstAsync();
+            var groupInfo = await _delegationRep.Query(x => x.Id == dto.DiId).FirstAsync();
+            var groupInfoData = new
+            {
+                groupInfo.Id,
+                groupInfo.TeamName,
+                groupInfo.TourCode,
+                groupInfo.ClientName,
+                VisitCountry = groupInfo.VisitCountry.Replace("|", "、"),
+                groupInfo.VisitDays,
+                groupInfo.VisitPNumber,
+                groupInfo.VisitStartDate,
+                groupInfo.VisitEndDate,
+                IsUploadFile = groupInfo.FrFilePaths != null && groupInfo.FrFilePaths.Count > 0
+            };
 
 
             //应收款项