feat(资金流水): 添加外部银行流水导入功能
- 新增 ExternalCashFlowService 用于调用外部银行流水接口 - 在出入金列表页面添加获取银行流水按钮 - 新增 BankFlowId 字段存储银行流水ID - 实现银行流水数据自动导入为出入金记录 - 添加相关配置项和模型类
This commit is contained in:
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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.结算管理_出入金新增)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user