douhandong 3 weeks ago
commit d99cb14ff8

@ -524,7 +524,7 @@ namespace DS.Module.Core
/// 打托
/// </summary>
[Description("打托")]
WAIT_DATUO = 316,
WAIT_DATUO = 317,
#endregion
#region 工作流-流程类型枚举

@ -24,7 +24,7 @@ namespace DS.WMS.Core.Application.Method
public abstract TaskBaseTypeEnum AuditType { get; }
readonly IClientFlowInstanceService flowService;
readonly IAuditTaskService taskService;
readonly IApplicationTaskService taskService;
/// <summary>
/// 初始化
@ -33,7 +33,7 @@ namespace DS.WMS.Core.Application.Method
public ApplicationAuditService(IServiceProvider serviceProvider) : base(serviceProvider)
{
flowService = serviceProvider.GetRequiredService<IClientFlowInstanceService>();
taskService = serviceProvider.GetRequiredService<IAuditTaskService>();
taskService = serviceProvider.GetRequiredService<IApplicationTaskService>();
}
/// <summary>

@ -97,7 +97,7 @@ namespace DS.WMS.Core.Application.Method
protected override DataResult PreAudit(List<InvoiceApplication> applications)
{
if (applications.Exists(x => x.Status != InvoiceApplicationStatus.AuditSubmittd))
if (applications.Exists(x => x.Status != InvoiceApplicationStatus.AuditSubmittd && x.Status != InvoiceApplicationStatus.AuditPassed))
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.ApplicationIsNotAuditing));
return DataResult.Success;

@ -289,7 +289,7 @@ namespace DS.WMS.Core.Application.Method
protected override DataResult PreAudit(List<PaymentApplication> applications)
{
if (applications.Exists(x => x.Status != PaymentApplicationStatus.AuditSubmittd))
if (applications.Exists(x => x.Status != PaymentApplicationStatus.AuditSubmittd && x.Status != PaymentApplicationStatus.AuditPassed))
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.ApplicationIsNotAuditing));
return DataResult.Success;

@ -395,7 +395,7 @@ namespace DS.WMS.Core.Fee.Method
var query1 = TenantDb.Queryable<FeeRecord>().Where(f => f.BusinessId == request.Id && f.BusinessType == request.BusinessType)
.InnerJoin<SeaExport>((f, s) => f.BusinessId == s.Id)
.LeftJoin<InfoClient>((f, s, i) => f.CustomerId == i.Id)
.WhereIF(ids1.Length > 0, (f, s, i) => (ids1.Contains(f.Id) || ids2.Contains(f.BusinessId)) && AuditStatusArray.Contains(f.FeeStatus))
.WhereIF(ids1.Length > 0, (f, s, i) => ids1.Contains(f.Id) || ids2.Contains(f.BusinessId))
.Select((f, s, i) => new FeeAuditItemQuery
{
Id = f.Id,

@ -908,7 +908,9 @@ namespace DS.WMS.Core.Fee.Method
if (taskType == TaskBaseTypeEnum.BILL_RECV_AUDIT)
{
if (entity.BillAuditStatus != BillAuditStatus.Pending && entity.BillAuditStatus != BillAuditStatus.RecvRejected)
if (entity.BillAuditStatus == BillAuditStatus.PaySubmitted || entity.BillAuditStatus == BillAuditStatus.PayRejected)
taskType = TaskBaseTypeEnum.BILL_PAY_AUDIT;
else if (entity.BillAuditStatus != BillAuditStatus.Pending && entity.BillAuditStatus != BillAuditStatus.RecvRejected)
return DataResult.Failed(string.Format(
MultiLanguageConst.GetDescription(nameof(MultiLanguageConst.BillFeeStatusError)), entity.BillAuditStatus.GetDescription()));
}
@ -985,11 +987,6 @@ namespace DS.WMS.Core.Fee.Method
x.ARFeeStatus,
x.APFeeStatus
}).ExecuteCommandAsync();
//修改关联费用状态为提交审核
await TenantDb.Updateable<FeeRecord>().Where(x => x.BusinessId == bid && x.BusinessType == type &&
(x.FeeStatus == FeeStatus.Entering || x.FeeStatus == FeeStatus.Withdraw || x.FeeStatus == FeeStatus.RejectSubmission))
.SetColumns(x => x.FeeStatus == FeeStatus.AuditSubmitted).ExecuteCommandAsync();
}
else if (taskType == TaskBaseTypeEnum.BILL_PAY_AUDIT)
{
@ -1000,6 +997,11 @@ namespace DS.WMS.Core.Fee.Method
}).ExecuteCommandAsync();
}
//修改关联费用状态为提交审核
await TenantDb.Updateable<FeeRecord>().Where(x => x.BusinessId == bid && x.BusinessType == type &&
(x.FeeStatus == FeeStatus.Entering || x.FeeStatus == FeeStatus.Withdraw || x.FeeStatus == FeeStatus.RejectSubmission))
.SetColumns(x => x.FeeStatus == FeeStatus.AuditSubmitted).ExecuteCommandAsync();
if (useTransaction)
await TenantDb.Ado.CommitTranAsync();
return DataResult.Success;
@ -1039,7 +1041,9 @@ namespace DS.WMS.Core.Fee.Method
if (taskType == TaskBaseTypeEnum.BILL_RECV_AUDIT)
{
if (entity.BillAuditStatus != BillAuditStatus.RecvSubmitted)
if (entity.BillAuditStatus == BillAuditStatus.PaySubmitted)
taskType = TaskBaseTypeEnum.BILL_PAY_AUDIT;
else if (entity.BillAuditStatus != BillAuditStatus.RecvSubmitted)
return DataResult.Failed(string.Format(
MultiLanguageConst.GetDescription(nameof(MultiLanguageConst.BusinessStatusError)), entity.BillAuditStatus.GetDescription()));
}

@ -11,15 +11,23 @@ namespace DS.WMS.Core.Fee.Method.ReportProviders
/// <summary>
/// 费用确认单
/// </summary>
public class DebitNoteReport : IReportProvider
public class DebitNoteReport : ServiceBase, IReportProvider
{
/// <summary>
/// 初始化
/// </summary>
/// <param name="provider"></param>
public DebitNoteReport(IServiceProvider provider) : base(provider)
{
}
public async Task<dynamic?> GetDataAsync(ReportContext context)
{
DebitNote? form = null;
switch (context.BusinessType)
{
case BusinessType.OceanShippingExport:
form = await context.TenantDb.Queryable<SeaExport>().Where(s => s.Id == context.BusinessId).Select<DebitNote>().FirstAsync();
form = await AsQueryable<SeaExport>().Where(s => s.Id == context.BusinessId).Select<DebitNote>().FirstAsync();
break;
case BusinessType.OceanShippingImport:
@ -32,7 +40,7 @@ namespace DS.WMS.Core.Fee.Method.ReportProviders
if (form != null)
{
form.Items = await context.TenantDb.Queryable<FeeRecord>().Where(x => x.BusinessId == context.BusinessId && x.BusinessType == context.BusinessType)
form.Items = await AsQueryable<FeeRecord>().Where(x => x.BusinessId == context.BusinessId && x.BusinessType == context.BusinessType)
.WhereIF(context.Ids != null && context.Ids.Length > 0, x => context.Ids.Contains(x.Id))
.Select(x => new DebitNoteItem
{

@ -12,17 +12,25 @@ namespace DS.WMS.Core.Fee.Method.ReportProviders
/// <summary>
/// 利润核算单
/// </summary>
public class ProfitAccountingReport : IReportProvider
public class ProfitAccountingReport : ServiceBase, IReportProvider
{
internal static readonly string[] CustomerTypes = ["booking", "controller", "shippercn", "yard", "truck", "custom"];
/// <summary>
/// 初始化
/// </summary>
/// <param name="provider"></param>
public ProfitAccountingReport(IServiceProvider provider) : base(provider)
{
}
public async Task<dynamic?> GetDataAsync(ReportContext context)
{
ProfitAccounting? form = null;
switch (context.BusinessType)
{
case BusinessType.OceanShippingExport:
form = await context.TenantDb.Queryable<SeaExport>().Where(s => s.Id == context.BusinessId)
form = await AsQueryable<SeaExport>().Where(s => s.Id == context.BusinessId)
.Select(s => new ProfitAccounting
{
ETDValue = s.ETD,
@ -71,7 +79,7 @@ namespace DS.WMS.Core.Fee.Method.ReportProviders
form.CabinRemark = string.Join(delimiter, remarks.Where(x => x.RemarkType == "2").Select(x => x.Remark));
form.BookingRemark = string.Join(delimiter, remarks.Where(x => x.RemarkType == "3").Select(x => x.Remark));
var list = await context.TenantDb.Queryable<FeeRecord>().Where(x => x.BusinessId == context.BusinessId && x.BusinessType == context.BusinessType)
var list = await AsQueryable<FeeRecord>().Where(x => x.BusinessId == context.BusinessId && x.BusinessType == context.BusinessType)
.WhereIF(context.Ids != null && context.Ids.Length > 0, x => context.Ids.Contains(x.Id))
.OrderByDescending(x => x.Currency).Select<FeeRecordRes>().ToListAsync();
if (list.Count > 0)

@ -7,6 +7,8 @@ using DS.WMS.Core.Flow.Interface;
using DS.WMS.Core.Op.Entity;
using DS.WMS.Core.Sys.Entity;
using Mapster;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using SqlSugar;
@ -24,12 +26,14 @@ public class FlowInstanceService : ServiceBase, IFlowInstanceService
api = new ApiFox();
}
ILogger<FlowInstanceService> logger;
/// <summary>
/// 初始化
/// </summary>
/// <param name="serviceProvider"></param>
public FlowInstanceService(IServiceProvider serviceProvider) : base(serviceProvider)
{
logger = serviceProvider.GetRequiredService<ILogger<FlowInstanceService>>();
}
/// <summary>
@ -615,7 +619,7 @@ public class FlowInstanceService : ServiceBase, IFlowInstanceService
}
else
{
await new ApplicationException($"访问回调URL{instance.CallbackURL} 时返回了错误:" + result.Message).LogAsync(Db);
logger.LogError($"访问回调URL{instance?.CallbackURL} 时返回了错误:" + result?.Message);
}
}
catch (Exception ex)

@ -10,11 +10,6 @@
/// </summary>
public long ApplicationId { get; set; }
/// <summary>
/// 发票申请币别
/// </summary>
public string Currency { get; set; }
/// <summary>
/// 本次RMB开票金额
/// </summary>

@ -316,32 +316,20 @@ namespace DS.WMS.Core.Invoice.Method
Currency = invoice.Currency,
OriginalCurrency = item.Currency,
ApplyAmount = item.ApplyAmount - item.ProcessedAmount,
OriginalAmount = item.OriginalAmount - item.OriginalProcessedAmount
OriginalAmount = item.OriginalAmount - item.OriginalProcessedAmount,
ExchangeRate = 1
};
var app = request.Applications.Find(x => x.ApplicationId == item.ApplicationId);
if (app != null)
if (app != null) //设置汇率
{
if (app.Currency == invoice.Currency)
var er = app.ExchangeRates?.FirstOrDefault(x => x.Currency == detail.OriginalCurrency);
if (er != null)
{
detail.ExchangeRate = 1m;
}
else if (string.IsNullOrEmpty(app.Currency)) //原币申请
{
detail.ExchangeRate = app.ExchangeRates.FirstOrDefault(x => x.Currency == item.Currency)?.ExchangeRate;
}
else
{
detail.ExchangeRate = app.ExchangeRates.FirstOrDefault(x => x.Currency == invoice.Currency)?.ExchangeRate;
detail.ExchangeRate = er.ExchangeRate;
}
}
if (!detail.ExchangeRate.HasValue)
detail.ExchangeRate = 1m;
if (detail.ExchangeRate.HasValue)
detail.ApplyAmount *= detail.ExchangeRate.Value;
invoice.Details.Add(detail);
}
@ -598,20 +586,22 @@ namespace DS.WMS.Core.Invoice.Method
if (invoiceAmount == 0)
return DataResult.Failed("开票金额不能为零");
var totalRMB = details.Sum(x => x.ApplyAmount);
if (Math.Abs(invoiceAmount) > totalRMB)
var totalAmount = details.Sum(x => x.OriginalAmount);
if (Math.Abs(invoiceAmount) > totalAmount)
return DataResult.Failed("申请开票金额不能大于剩余开票金额");
if (totalRMB != invoiceAmount) //非全额开票
if (totalAmount != invoiceAmount) //非全额开票
{
var rest = totalRMB - invoiceAmount;
var rest = totalAmount - invoiceAmount;
foreach (var detail in details)
{
if (rest == 0)
break;
detail.ApplyAmount = detail.ApplyAmount - rest;
rest = detail.ApplyAmount - rest;
detail.OriginalAmount = detail.OriginalAmount - rest;
rest = detail.OriginalAmount - rest;
detail.ApplyAmount = detail.OriginalAmount * detail.ExchangeRate.GetValueOrDefault();
}
}

@ -66,7 +66,7 @@ namespace DS.WMS.Core.Op.Entity
/// Desc:是否费用锁定
/// </summary>
[SugarColumn(ColumnDescription = "是否费用锁定", DefaultValue = "0")]
public bool? IsFeeLocking { get; set; } = false;
public bool? IsFeeLocking { get; set; } = false;
/// <summary>
/// 整单费用审核状态

@ -140,11 +140,11 @@ namespace DS.WMS.Core.Settlement.Method
{
detail.Currency = settlement.Currency;
var doc = request.Documents.Find(x => x.Id == detail.ApplicationId);
var doc = request.Documents.Find(x => x.Id == detail.RefId);
if (doc == null)
return DataResult<TEntity>.Failed("结算单据与费用明细不一致");
var exchange = doc.ExchangeRates?.Find(x => x.Currency == settlement.Currency);
var exchange = doc.ExchangeRates?.Find(x => x.Currency == detail.OriginalCurrency);
if (exchange == null)
return DataResult<TEntity>.Failed($"未传入结算币别 {settlement.Currency} 与费用原币别 {detail.OriginalCurrency} 之间的汇率信息");
@ -250,14 +250,14 @@ namespace DS.WMS.Core.Settlement.Method
details1.Where(x => x.RefId == doc.Id).OrderBy(x => x.ApplyAmount) :
details1.Where(x => x.BusinessId == doc.Id && x.BusinessType == doc.BusinessType && x.CustomerName == doc.CustomerName).OrderBy(x => x.ApplyAmount);
var rmbDetails = details2.Where(x => x.Currency == FeeCurrency.RMB_CODE);
var totalRMB = rmbDetails.Sum(x => x.ApplyAmount);
var rmbDetails = details2.Where(x => x.OriginalCurrency == FeeCurrency.RMB_CODE);
var totalRMB = rmbDetails.Sum(x => x.OriginalAmount);
doc.SettlementRMB ??= totalRMB;
if (doc.SettlementRMB > totalRMB)
return DataResult<TEntity>.Failed("人民币结算金额不能大于剩余人民币金额");
if (totalRMB < doc.SettlementRMB)
if (doc.SettlementRMB < totalRMB)
{
var rest = totalRMB - doc.SettlementRMB.GetValueOrDefault();
foreach (var detail in rmbDetails)
@ -265,19 +265,29 @@ namespace DS.WMS.Core.Settlement.Method
if (rest == 0)
break;
detail.ApplyAmount = detail.ApplyAmount - rest;
rest = detail.ApplyAmount - rest;
detail.OriginalAmount = detail.OriginalAmount - rest;
if (detail.OriginalCurrency == settlement.Currency)
{
detail.ApplyAmount = detail.OriginalAmount;
}
else
{
var er = doc.ExchangeRates.Find(x => x.Currency == detail.OriginalCurrency);
detail.ApplyAmount = detail.OriginalAmount * er?.ExchangeRate ?? 1;
}
rest = detail.OriginalAmount - rest;
}
}
var usdDetails = details2.Where(x => x.Currency == FeeCurrency.USD_CODE);
var totalUSD = usdDetails.Sum(x => x.ApplyAmount);
var usdDetails = details2.Where(x => x.OriginalCurrency == FeeCurrency.USD_CODE);
var totalUSD = usdDetails.Sum(x => x.OriginalAmount);
doc.SettlementUSD ??= totalUSD;
if (doc.SettlementUSD > totalUSD)
return DataResult<TEntity>.Failed("美元结算金额不能大于剩余美元金额");
if (totalUSD < doc.SettlementUSD)
if (doc.SettlementUSD < totalUSD)
{
var rest = totalUSD - doc.SettlementUSD.GetValueOrDefault();
foreach (var detail in usdDetails)
@ -285,19 +295,28 @@ namespace DS.WMS.Core.Settlement.Method
if (rest == 0)
break;
detail.ApplyAmount = detail.ApplyAmount - rest;
rest = detail.ApplyAmount - rest;
detail.OriginalAmount = detail.OriginalAmount - rest;
if (detail.OriginalCurrency == settlement.Currency)
{
detail.ApplyAmount = detail.OriginalAmount;
}
else
{
var er = doc.ExchangeRates.Find(x => x.Currency == detail.OriginalCurrency);
detail.ApplyAmount = detail.OriginalAmount * er?.ExchangeRate ?? 1;
}
rest = detail.OriginalAmount - rest;
}
}
var otherDetails = details2.Where(x => x.Currency != FeeCurrency.RMB_CODE && x.Currency != FeeCurrency.USD_CODE);
var total = rmbDetails.Sum(x => x.ApplyAmount);
var otherDetails = details2.Where(x => x.OriginalCurrency != FeeCurrency.RMB_CODE && x.Currency != FeeCurrency.USD_CODE);
var total = otherDetails.Sum(x => x.OriginalAmount);
doc.SettlementOther ??= total;
if (doc.SettlementOther > total)
return DataResult<TEntity>.Failed("其他结算金额不能大于剩余其他金额");
if (total < doc.SettlementOther)
if (doc.SettlementOther < total)
{
var rest = total - doc.SettlementOther.GetValueOrDefault();
foreach (var detail in otherDetails)
@ -305,8 +324,17 @@ namespace DS.WMS.Core.Settlement.Method
if (rest == 0)
break;
detail.ApplyAmount = detail.ApplyAmount - rest;
rest = detail.ApplyAmount - rest;
detail.OriginalAmount = detail.OriginalAmount - rest;
if (detail.OriginalCurrency == settlement.Currency)
{
detail.ApplyAmount = detail.OriginalAmount;
}
else
{
var er = doc.ExchangeRates.Find(x => x.Currency == detail.OriginalCurrency);
detail.ApplyAmount = detail.OriginalAmount * er?.ExchangeRate ?? 1;
}
rest = detail.OriginalAmount - rest;
}
}
}

@ -0,0 +1,10 @@
namespace DS.WMS.Core.TaskInteraction.Interface
{
/// <summary>
/// 申请类审核任务
/// </summary>
public interface IApplicationTaskService : IAuditTaskService
{
}
}

@ -0,0 +1,102 @@
using DS.Module.Core;
using DS.WMS.Core.Flow.Dtos;
using DS.WMS.Core.Flow.Entity;
using DS.WMS.Core.TaskInteraction.Dtos;
using DS.WMS.Core.TaskInteraction.Entity;
using DS.WMS.Core.TaskInteraction.Interface;
namespace DS.WMS.Core.TaskInteraction.Method
{
/// <summary>
/// 申请类审核任务
/// </summary>
public class ApplicationTaskService : TaskService, IApplicationTaskService
{
/// <summary>
/// 初始化
/// </summary>
/// <param name="provider"></param>
public ApplicationTaskService(IServiceProvider provider) : base(provider)
{
}
public async override Task<DataResult> AuditAsync(TaskAuditRequest request)
{
var tasksList = await TenantDb.Queryable<BusinessTask>().Where(x => x.TaskType == request.TaskType && request.Ids.Contains(x.BusinessId))
.WhereIF(request.BusinessType.HasValue, x => x.BusinessType == request.BusinessType.Value).ToListAsync();
if (tasksList.Count == 0)
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.TaskNotExists));
if (tasksList.Count != request.Ids.Length)
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.TaskCountNotMatch));
if (tasksList.Exists(x => x.TaskStatus != TaskStatusEnum.Create && x.TaskStatus != TaskStatusEnum.Pending && x.TaskStatus != TaskStatusEnum.Complete))
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.TaskAuditStatusError));
DataResult result = DataResult.Success;
var tasks = tasksList.FindAll(x => x.TaskStatus == TaskStatusEnum.Create);
//var task2 = tasksList.FindAll(x => x.TaskStatus == TaskStatusEnum.Complete);
await TenantDb.Ado.BeginTranAsync();
try
{
//状态待审核的任务
if (tasksList.Count > 0)
{
var flowIds = tasks.Select(x => x.FlowId.Value);
var flowInstances = await Db.Queryable<FlowInstance>().Where(x => flowIds.Contains(x.Id)).ToListAsync();
foreach (var instance in flowInstances)
{
result = await FlowService.Value.AuditAsync(new FlowAuditInfo
{
AuditNote = request.Remark,
Status = request.Result,
Instance = instance
});
if (!result.Succeeded)
return result;
var req = new TaskUpdateRequest
{
BusinessId = instance.BusinessId,
BusinessType = instance.BusinessType,
RejectReason = request.Remark,
TaskTypeName = request.TaskType.ToString(),
TaskStatus = request.Result == 1 ? TaskStatusEnum.Complete : TaskStatusEnum.Pending,
AutoCreateNext = true
};
//根据审批结果更新任务状态
await SetTaskStatusAsync(req, false);
if (instance.FlowStatus == FlowStatusEnum.Reject)
result.Message = request.Remark!;
}
}
////已完成审核的任务
//if (task2.Count > 0)
//{
// switch (request.TaskType)
// {
// case TaskBaseTypeEnum.APPLICATION_PAYMENT_AUDIT:
// break;
// case TaskBaseTypeEnum.APPLICATION_INVOICE_AUDIT:
// break;
// default:
// return DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed));
// }
//}
await TenantDb.Ado.CommitTranAsync();
return result;
}
catch (Exception ex)
{
await TenantDb.Ado.RollbackTranAsync();
await ex.LogAsync(Db);
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed));
}
}
}
}

@ -66,8 +66,8 @@ namespace DS.WMS.Core.TaskInteraction.Method
BusinessType = x.BusinessType,
BillAuditStatus = x.BillAuditStatus,
BillFeeStatusTime = x.BillFeeStatusTime,
ProfitMargin = SqlFunc.Subqueryable<FeeRecord>().Where(f => f.BusinessId == x.BusinessId && f.BusinessType == x.BusinessType && f.FeeType == FeeType.Receivable).Sum(f => f.Amount)
- SqlFunc.Subqueryable<FeeRecord>().Where(f => f.BusinessId == x.BusinessId && f.BusinessType == x.BusinessType && f.FeeType == FeeType.Payable).Sum(f => f.Amount)
ProfitMargin = SqlFunc.Subqueryable<FeeRecord>().Where(f => f.BusinessId == x.BusinessId && f.BusinessType == x.BusinessType && f.FeeType == FeeType.Receivable).Sum(f => f.Amount * SqlFunc.IsNull(f.ExchangeRate.Value, 1))
- SqlFunc.Subqueryable<FeeRecord>().Where(f => f.BusinessId == x.BusinessId && f.BusinessType == x.BusinessType && f.FeeType == FeeType.Payable).Sum(f => f.Amount * SqlFunc.IsNull(f.ExchangeRate.Value, 1))
}).FirstAsync();
if (biz == null)
return;
@ -173,7 +173,7 @@ namespace DS.WMS.Core.TaskInteraction.Method
{
if (biz.ProfitMargin > 0)
{
biz.IsBusinessLocking = true;
biz.IsFeeLocking = true;
await SetLockAsync(biz);
}
else //如果为非正利润则生成锁单任务
@ -345,6 +345,7 @@ namespace DS.WMS.Core.TaskInteraction.Method
BusinessType = callback.BusinessType,
TaskTypeName = GetRejectedType(callback.AuditType.Value).ToString()!,
TaskTitle = $"【{callback.AuditType.GetDescription()}】【{callback.BusinessType.GetDescription()}】{business.CustomerNo}",
TaskDescription = "审核备注:" + callback.RejectReason,
RecvUserIdList = [task.CreateBy] //通知任务发起人
};
//创建驳回任务以进行通知

Loading…
Cancel
Save