feat(资金流水): 添加外部银行流水导入功能

- 新增 ExternalCashFlowService 用于调用外部银行流水接口
- 在出入金列表页面添加获取银行流水按钮
- 新增 BankFlowId 字段存储银行流水ID
- 实现银行流水数据自动导入为出入金记录
- 添加相关配置项和模型类
This commit is contained in:
shangzhongyuan
2026-03-13 11:15:19 +08:00
parent c53d1554ce
commit 6b89699212
10 changed files with 437 additions and 3 deletions
+1
View File
@@ -226,3 +226,4 @@ logfile
/.idea
/.idea/.idea.YLerpbase/.idea
/NuGet.Config
/.trae
@@ -109,6 +109,12 @@ namespace YLErp.DBModels
[DisplayName("流水号")]
public string SerialNumber { get; set; }
/// <summary>
/// 银行流水ID
/// </summary>
[DisplayName("银行流水ID")]
public string BankFlowId { get; set; }
[DisplayName("账户余额")]
public double? AccountBalance { get; set; }
@@ -255,6 +261,7 @@ namespace YLErp.DBModels
public const string _预付金返息 = "系统操作-预付金返息";
public const string _其他 = "人工操作-其他";
public const string _预付金 = "人工操作-预付金";
public const string = "系统";
/// <summary>
/// all action
@@ -1,5 +1,7 @@
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE `ClientCashInCashOut` ADD COLUMN `BankFlowId` varchar(100) NULL DEFAULT NULL COMMENT '银行流水ID' AFTER `SerialNumber`;
CREATE TABLE `client_variety_span_cfg` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键Id',
`ValueDate` date NOT NULL COMMENT '生效日期',
@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
namespace YLErp.Model.ExternalCashFlow
{
/// <summary>
/// 外部资金流水接口响应模型
/// </summary>
public class ExternalCashFlowResponse
{
/// <summary>
/// 响应码
/// </summary>
public string code { get; set; }
/// <summary>
/// 响应消息
/// </summary>
public string message { get; set; }
/// <summary>
/// 响应数据
/// </summary>
public List<ExternalCashFlowData> data { get; set; }
}
/// <summary>
/// 外部资金流水数据模型
/// </summary>
public class ExternalCashFlowData
{
/// <summary>
/// 流水 ID
/// </summary>
public string flowId { get; set; }
/// <summary>
/// 账号
/// </summary>
public string account { get; set; }
/// <summary>
/// 金额
/// </summary>
public string amount { get; set; }
/// <summary>
/// 户名
/// </summary>
public string name { get; set; }
/// <summary>
/// 银行名称
/// </summary>
public string bankName { get; set; }
/// <summary>
/// 币种
/// </summary>
public string currency { get; set; }
/// <summary>
/// 入账日期(可能为 null
/// </summary>
public string entryDate { get; set; }
/// <summary>
/// 交易时间
/// </summary>
public string transTime { get; set; }
/// <summary>
/// 借贷标识:D-借,C-贷
/// </summary>
public string debitCreditMark { get; set; }
/// <summary>
/// 用途描述(可能为 null
/// </summary>
public string useDescribe { get; set; }
/// <summary>
/// 补充信息(可能为 null
/// </summary>
public string supplementaryDetails { get; set; }
/// <summary>
/// 对方账户
/// </summary>
public string counterpartyAccount { get; set; }
/// <summary>
/// 对方户名
/// </summary>
public string counterpartyName { get; set; }
/// <summary>
/// 对方银行名称
/// </summary>
public string counterpartyBankName { get; set; }
}
/// <summary>
/// 外部资金流水接口请求参数
/// </summary>
public class ExternalCashFlowRequest
{
/// <summary>
/// 令牌
/// </summary>
public string token { get; set; }
/// <summary>
/// 账号
/// </summary>
public string account { get; set; }
/// <summary>
/// 开始日期
/// </summary>
public string beginDate { get; set; }
/// <summary>
/// 结束日期
/// </summary>
public string endDate { get; set; }
/// <summary>
/// 客户ID
/// </summary>
public int? clientId { get; set; }
}
}
@@ -0,0 +1,25 @@
namespace YLErp.Services.CashRecordModule
{
/// <summary>
/// 外部资金流水配置选项
/// </summary>
public class ExternalCashFlowOptions
{
public const string SectionName = "ExternalCashFlow";
/// <summary>
/// 令牌
/// </summary>
public string Token { get; set; }
/// <summary>
/// 基础URL
/// </summary>
public string BaseUrl { get; set; }
/// <summary>
/// 超时秒数
/// </summary>
public int TimeoutSeconds { get; set; } = 60;
}
}
@@ -0,0 +1,109 @@
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using YLErp.Model.ExternalCashFlow;
using Qdp.Foundation.Utilities;
using YLErp.Modules.ApiModule;
using Microsoft.Extensions.Options;
namespace YLErp.Services.CashRecordModule
{
/// <summary>
/// 外部资金流水接口调用服务
/// </summary>
public interface IExternalCashFlowService
{
/// <summary>
/// 获取资金流水数据
/// </summary>
/// <param name="token">令牌</param>
/// <param name="account">账号</param>
/// <param name="beginDate">开始日期</param>
/// <param name="endDate">结束日期</param>
/// <returns>资金流水数据</returns>
Task<ExternalCashFlowResponse> GetCashFlowDataAsync(string token, string account, string beginDate, string endDate);
}
/// <summary>
/// 外部资金流水接口调用服务实现
/// </summary>
public class ExternalCashFlowService : IExternalCashFlowService
{
private static readonly IYcLogger logger = LogFactory.GetLogger<ExternalCashFlowService>();
private readonly string _baseUrl;
private readonly int _timeoutSeconds;
public ExternalCashFlowService(IOptions<ExternalCashFlowOptions> options)
{
var config = options?.Value ?? new ExternalCashFlowOptions();
_baseUrl = config.BaseUrl ?? "http://10.99.56.25:8082";
_timeoutSeconds = config.TimeoutSeconds > 0 ? config.TimeoutSeconds : 60;
}
/// <summary>
/// 获取资金流水数据
/// </summary>
/// <param name="token">令牌</param>
/// <param name="account">账号</param>
/// <param name="beginDate">开始日期</param>
/// <param name="endDate">结束日期</param>
/// <returns>资金流水数据</returns>
public async Task<ExternalCashFlowResponse> GetCashFlowDataAsync(string token, string account, string beginDate, string endDate)
{
try
{
// 构建请求 URL
var apiUrl = $"{_baseUrl}/eusp/queryStatement";
var queryString = $"?token={Uri.EscapeDataString(token)}" +
$"&account={Uri.EscapeDataString(account)}" +
$"&beginDate={Uri.EscapeDataString(beginDate)}" +
$"&endDate={Uri.EscapeDataString(endDate)}";
var requestUrl = apiUrl + queryString;
logger.Info($"调用外部资金流水接口:{requestUrl}");
// 创建 HttpClient 并设置超时
using (var httpClient = new HttpClient())
{
httpClient.Timeout = TimeSpan.FromSeconds(_timeoutSeconds);
// 发送 GET 请求
var response = await httpClient.GetAsync(requestUrl);
response.EnsureSuccessStatusCode();
// 读取响应内容
var responseContent = await response.Content.ReadAsStringAsync();
logger.Info($"外部资金流水接口返回:{responseContent}");
// 反序列化为响应对象
var responseObj = JsonHelper.Deserialize<ExternalCashFlowResponse>(responseContent);
if (responseObj == null)
{
logger.Error("外部资金流水接口响应为空");
throw new Exception("外部资金流水接口响应为空");
}
if (responseObj.code != "0")
{
logger.Error($"外部资金流水接口调用失败:code={responseObj.code}, message={responseObj.message}");
throw new Exception($"外部资金流水接口调用失败:{responseObj.message}");
}
return responseObj;
}
}
catch (TaskCanceledException ex)
{
logger.Error($"调用外部资金流水接口超时:{ex.Message}", ex);
throw new Exception("调用外部资金流水接口超时,请检查网络连接");
}
catch (Exception ex)
{
logger.Error($"调用外部资金流水接口失败:{ex.Message}", ex);
throw new Exception($"调用外部资金流水接口失败:{ex.Message}");
}
}
}
}
+116 -2
View File
@@ -1,6 +1,10 @@
using YLErp.Abstract;
using YLErp.Abstract;
using YLErp.DBModels;
using YLErp.Modules.ClientModule;
using YLErp.Modules.TradeModule.QueryModule;
using YLErp.Services.CashRecordModule;
using YLErp.Model.ExternalCashFlow;
using Microsoft.Extensions.Configuration;
namespace YLErp.Web.Controllers
{
@@ -8,10 +12,14 @@ namespace YLErp.Web.Controllers
{
readonly IViewRenderService _viewRenderer;
private IKafkaProduce kafkaProduceHelper;
public entryexitController(IViewRenderService viewRenderer,IKafkaProduce kafkaProduce)
private readonly IExternalCashFlowService _externalCashFlowService;
private readonly IConfiguration _configuration;
public entryexitController(IViewRenderService viewRenderer,IKafkaProduce kafkaProduce, IExternalCashFlowService externalCashFlowService, IConfiguration configuration)
{
_viewRenderer = viewRenderer;
kafkaProduceHelper = kafkaProduce;
_externalCashFlowService = externalCashFlowService;
_configuration = configuration;
}
[MyAuthorize("结算管理-出入金维护")]
public ActionResult entryexitList()
@@ -510,5 +518,111 @@ namespace YLErp.Web.Controllers
var sList = new ClientCashInCashOutDataService(CurUser).EntryexitApprovalQuery(req);
return Json(sList);
}
/// <summary>
/// 获取银行流水数据
/// </summary>
/// <param name="req">请求参数</param>
/// <returns>银行流水数据</returns>
[HttpPost]
public async Task<JsonResult> GetBankCashFlow(ExternalCashFlowRequest req)
{
try
{
var token = _configuration["ExternalCashFlow:Token"] ?? req.token;
var result = await _externalCashFlowService.GetCashFlowDataAsync(token, req.account, req.beginDate, req.endDate);
if (result?.data == null || result.data.Count == 0)
{
return JsonSuccess("未获取到银行流水数据");
}
var clientId = req.clientId;
if (!clientId.HasValue || clientId == 0)
{
return JsonError("请选择客户");
}
var existingFlowIds = yldb.ClientCashInCashOut
.Where(c => !string.IsNullOrEmpty(c.BankFlowId))
.Select(c => c.BankFlowId)
.ToHashSet();
var service = new ClientCashInCashOutDataService(CurUser);
var saveCount = 0;
var skipCount = 0;
var duplicateCount = 0;
var invalidCounterpartyCount = 0;
foreach (var flow in result.data)
{
if (string.IsNullOrEmpty(flow.transTime))
{
skipCount++;
continue;
}
if (string.IsNullOrEmpty(flow.counterpartyAccount))
{
invalidCounterpartyCount++;
continue;
}
if (existingFlowIds.Contains(flow.flowId))
{
duplicateCount++;
continue;
}
// 根据 counterpartyAccount 查找对应的 BankCard
var bankcard = clientDB.bankcard
.Where(b => b.Card == flow.counterpartyAccount && b.ValidState != "InValid")
.FirstOrDefault();
if (bankcard == null)
{
invalidCounterpartyCount++;
continue;
}
var happenDate = DateTime.TryParse(flow.transTime, out var dt) ? dt : DateTime.Now;
var direction = flow.debitCreditMark == "C" ? "入金" : "出金";
var amount = Math.Abs(double.TryParse(flow.amount, out var amt) ? amt : 0);
var cashRecord = new ClientCashInCashOut
{
ClientId = bankcard.ClientId,
ClientName = bankcard.ClientName,
Direction = direction,
Money = amount,
HappenDate = happenDate,
Action = ClientCashInCashOut.,
CurrencyCode = flow.currency ?? "CNY",
BankFlowId = flow.flowId,
SerialNumber = flow.flowId,
OpenBankCard = flow.counterpartyAccount,
Comments = $"银行流水导入 | 对方账号:{flow.counterpartyAccount} | 对方户名:{flow.counterpartyName} | 用途:{flow.useDescribe}"
};
try
{
service.SaveEntryexit(null, cashRecord);
existingFlowIds.Add(flow.flowId);
saveCount++;
}
catch (Exception ex)
{
LogFactory.GetLogger<entryexitController>().Error($"保存银行流水记录失败:{ex.Message}", ex);
}
}
return JsonSuccess($"获取银行流水成功,共处理{result.data.Count}条,成功保存{saveCount}条,跳过{skipCount}条(无效对手方账号{invalidCounterpartyCount}条,重复{duplicateCount}条)");
}
catch (Exception ex)
{
LogFactory.GetLogger<entryexitController>().Error("获取银行流水失败", ex);
return JsonError(ex.Message);
}
}
}
}
+2
View File
@@ -49,8 +49,10 @@ try
options.MultipartBodyLengthLimit = int.MaxValue; //不限制文件上传大小
});
builder.Services.Configure<KafkaConfig>(builder.Configuration.GetSection("KafkaConfig"));
builder.Services.Configure<YLErp.Services.CashRecordModule.ExternalCashFlowOptions>(builder.Configuration.GetSection("ExternalCashFlow"));
builder.Services.AddScoped<IViewRenderService, ViewRenderService>();
builder.Services.AddSingleton<IKafkaProduce, KafkaProduceHelper>();
builder.Services.AddTransient<YLErp.Services.CashRecordModule.IExternalCashFlowService, YLErp.Services.CashRecordModule.ExternalCashFlowService>();
builder.Services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
+38 -1
View File
@@ -1,4 +1,4 @@
@using YLErp
@using YLErp
@{
ViewBag.Title = "资金记录";
Layout = "~/Views/Shared/_MainLayout.cshtml";
@@ -672,6 +672,42 @@
$form.submit();
}
function getBankCashFlow() {
var beginDate = $("#DateFromHappenDate").val();
var endDate = $("#DateToHappenDate").val();
var clientId = $("#ClientId").val();
if (!beginDate || !endDate) {
main.message("请选择发生时间范围");
return;
}
function formatDate(dateStr) {
if (!dateStr) return "";
var date = new Date(dateStr);
var year = date.getFullYear();
var month = ("0" + (date.getMonth() + 1)).slice(-2);
var day = ("0" + date.getDate()).slice(-2);
return year + month + day;
}
main.post("/entryexit/GetBankCashFlow", {
clientId: clientId ? clientId[0] : null,
account: "",
beginDate: formatDate(beginDate),
endDate: formatDate(endDate)
}).done(function (data) {
if (data.success) {
main.message("获取银行流水成功");
console.log(data.obj);
} else {
main.message(data.msg);
}
}).fail(function (error) {
main.message("获取银行流水失败:" + error.responseText);
});
}
</script>
}
@@ -711,6 +747,7 @@
}
}
@MyControls.Btn("导出", "downEntryexit()")
@MyControls.Btn("获取银行流水", "getBankCashFlow()")
</div>
<div style="display:inline;margin-right:100px;">
@if (CurUser.结算管理_出入金新增)
+4
View File
@@ -88,5 +88,9 @@
"auth_url": "http://127.0.0.1:8888/sso/login", //SSO授权地址
"callback_url": "http://localhost:49462/Account/Callback", //回调处理地址
"get_user_url": "http://127.0.0.1:8888/sso/getSsoUser" //获取用户信息接口地址
},
"ExternalCashFlow": {
"Token": "50cd1507-5361-4cbc-9368-e848c69bb0f6",
"BaseUrl": "http://10.99.56.25:8082"
}
}