|
|
|
|
using System.Linq.Expressions;
|
|
|
|
|
using DS.Module.Core;
|
|
|
|
|
using DS.Module.Core.Data;
|
|
|
|
|
using DS.Module.Core.Extensions;
|
|
|
|
|
using DS.Module.DjyServiceStatus;
|
|
|
|
|
using DS.WMS.Core.Op.Entity;
|
|
|
|
|
using DS.WMS.Core.Sys.Entity;
|
|
|
|
|
using DS.WMS.Core.TaskInteraction.Dtos;
|
|
|
|
|
using DS.WMS.Core.TaskInteraction.Entity;
|
|
|
|
|
using DS.WMS.Core.TaskInteraction.Interface;
|
|
|
|
|
using DS.WMS.Core.TaskPlat.Dtos;
|
|
|
|
|
using Masuit.Tools.Systems;
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
using SqlSugar;
|
|
|
|
|
|
|
|
|
|
namespace DS.WMS.Core.TaskInteraction.Method
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 海运出口任务交互服务
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class SeaExportTaskService : TaskService, ISeaExportTaskService
|
|
|
|
|
{
|
|
|
|
|
const TaskBaseTypeEnum BL_CONFIRM_TASK = TaskBaseTypeEnum.WAIT_BILL_CONFIRM;
|
|
|
|
|
readonly Lazy<IDjyServiceStatusService> djyService;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 初始化
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="provider"></param>
|
|
|
|
|
public SeaExportTaskService(IServiceProvider provider) : base(provider)
|
|
|
|
|
{
|
|
|
|
|
djyService = new Lazy<IDjyServiceStatusService>(provider.GetRequiredService<IDjyServiceStatusService>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override Task<DataResult> CreateTaskAsync(TaskCreationRequest request, bool useTransaction = true)
|
|
|
|
|
{
|
|
|
|
|
if (request.TaskType == TaskBaseTypeEnum.WAIT_BILL_CONFIRM)
|
|
|
|
|
{
|
|
|
|
|
request.Steps ??= [];
|
|
|
|
|
if (!request.Steps.Exists(x => x.Type == StepType.OPConfirm))
|
|
|
|
|
{
|
|
|
|
|
request.Steps.Add(new TaskStepDto
|
|
|
|
|
{
|
|
|
|
|
Type = StepType.OPConfirm,
|
|
|
|
|
Name = StepType.OPConfirm.ToString()
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return base.CreateTaskAsync(request, useTransaction);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 创建关联子任务
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="request"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public async Task CreateSubTaskAsync(IEnumerable<TaskCreationRequest> request)
|
|
|
|
|
{
|
|
|
|
|
if (request == null || !request.Any())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var first = request.First();
|
|
|
|
|
var taskTypes = request.Select(x => x.TaskType).Distinct().ToArray();
|
|
|
|
|
var existTypes = await GetQuery(first.BusinessId, first.BusinessType, taskTypes).Select(x => x.TaskType).ToListAsync();
|
|
|
|
|
//移除已存在的任务类型
|
|
|
|
|
request = request.Where(x => !existTypes.Contains(x.TaskType));
|
|
|
|
|
if (!request.Any())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
long userId = long.Parse(User.UserId);
|
|
|
|
|
long tenatId = long.Parse(User.TenantId);
|
|
|
|
|
string tenatName = Db.Queryable<SysTenant>().Where(x => x.Id == tenatId).Select(x => x.Name).First();
|
|
|
|
|
var dt = DateTime.Now;
|
|
|
|
|
var defaultUsers = await FillInUserInfoAsync(first.RecvUserIdList);
|
|
|
|
|
var biz = await TenantDb.Queryable<SeaExport>().Select(x => new
|
|
|
|
|
{
|
|
|
|
|
x.Id,
|
|
|
|
|
x.CustomerNo,
|
|
|
|
|
x.MBLNO,
|
|
|
|
|
x.Vessel,
|
|
|
|
|
x.Voyno,
|
|
|
|
|
x.ETD,
|
|
|
|
|
|
|
|
|
|
x.DischargePortId,
|
|
|
|
|
x.DischargePortCode,
|
|
|
|
|
x.DischargePort,
|
|
|
|
|
x.LoadPortId,
|
|
|
|
|
x.LoadPortCode,
|
|
|
|
|
x.LoadPort,
|
|
|
|
|
|
|
|
|
|
x.CarrierId,
|
|
|
|
|
x.Carrier,
|
|
|
|
|
//x.BookingNo,
|
|
|
|
|
//x.HBLNO,
|
|
|
|
|
//x.CustomerNum,
|
|
|
|
|
}).FirstAsync(x => x.Id == first.BusinessId);
|
|
|
|
|
|
|
|
|
|
var taskList = new List<BusinessTask>();
|
|
|
|
|
var taskLogList = new List<BusinessTaskLog>();
|
|
|
|
|
//获取任务接收人
|
|
|
|
|
var dic = await GetRecvUsersAsync(first.BusinessId, first.BusinessType, request.Select(x => x.TaskType).ToList());
|
|
|
|
|
foreach (var item in request)
|
|
|
|
|
{
|
|
|
|
|
var recvUsers = dic[item.TaskType];
|
|
|
|
|
var info = new TaskManageOrderMessageInfo
|
|
|
|
|
{
|
|
|
|
|
Head = new TaskManageOrderMessageHeadInfo
|
|
|
|
|
{
|
|
|
|
|
GID = Guid.NewGuid().ToString(),
|
|
|
|
|
BSNO = item.BusinessId,
|
|
|
|
|
MessageType = "WORK_FLOW_TASK",
|
|
|
|
|
SenderId = "WorkFlow",
|
|
|
|
|
SenderName = "工作流平台",
|
|
|
|
|
ReceiverId = "TaskManage",
|
|
|
|
|
ReceiverName = "任务管理平台",
|
|
|
|
|
Version = "1.0",
|
|
|
|
|
RequestDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
|
|
|
|
|
RequestAction = "Add"
|
|
|
|
|
},
|
|
|
|
|
Main = new TaskManageOrderMessageMainInfo
|
|
|
|
|
{
|
|
|
|
|
TaskType = item.TaskType,
|
|
|
|
|
TaskSource = TaskSourceEnum.WORK_FLOW,
|
|
|
|
|
TaskUserId = User.UserId,
|
|
|
|
|
TaskUserName = User.UserName,
|
|
|
|
|
RecvUserInfoList = recvUsers ?? defaultUsers,
|
|
|
|
|
TaskTenatId = tenatId,
|
|
|
|
|
TaskTenatName = tenatName,
|
|
|
|
|
IsCheckExistsByTaskType = true
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
info.Main.TaskDesp = info.Main.TaskTitle = $"【{item.TaskType.GetDescription()}】{biz?.CustomerNo} {biz?.Vessel} {biz?.Voyno} ETD:{biz?.ETD?.ToString("yyyy-MM-dd")}";
|
|
|
|
|
info.Main.CustomerNo = biz?.CustomerNo;
|
|
|
|
|
info.Main.MBlNo = biz?.MBLNO;
|
|
|
|
|
info.Main.ETD = biz?.ETD;
|
|
|
|
|
|
|
|
|
|
info.Main.PortDischarge = biz?.DischargePort;
|
|
|
|
|
info.Main.PortDischargeCode = biz?.DischargePortCode;
|
|
|
|
|
info.Main.PortDischargeId = biz?.DischargePortId;
|
|
|
|
|
info.Main.PortLoad = biz?.LoadPort;
|
|
|
|
|
info.Main.PortLoadId = biz?.LoadPortId;
|
|
|
|
|
info.Main.PortLoadCode = biz?.LoadPortCode;
|
|
|
|
|
|
|
|
|
|
info.Main.CarrierId = biz?.Carrier;
|
|
|
|
|
info.Main.CarrierPK = biz?.CarrierId;
|
|
|
|
|
|
|
|
|
|
//info.Main.HBLNO = biz?.HBLNO;
|
|
|
|
|
//info.Main.BookingNo = biz?.BookingNo;
|
|
|
|
|
//info.Main.CustomsNum = biz?.CustomerNum;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var result = await ManagerService.InitTaskJob(info);
|
|
|
|
|
if (!result.Succeeded)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
var task = new BusinessTask
|
|
|
|
|
{
|
|
|
|
|
BusinessId = item.BusinessId,
|
|
|
|
|
BusinessType = item.BusinessType,
|
|
|
|
|
TaskType = item.TaskType,
|
|
|
|
|
TaskStatus = TaskStatusEnum.Create,
|
|
|
|
|
RecvUsers = info.Main.RecvUserInfoList == null ? string.Empty : string.Join(",", info.Main.RecvUserInfoList.Select(x => x.RecvUserId)),
|
|
|
|
|
CreateBy = userId,
|
|
|
|
|
CreateTime = dt
|
|
|
|
|
};
|
|
|
|
|
taskList.Add(task);
|
|
|
|
|
taskLogList.Add(new BusinessTaskLog
|
|
|
|
|
{
|
|
|
|
|
BusinessId = task.BusinessId,
|
|
|
|
|
BusinessType = task.BusinessType,
|
|
|
|
|
CreateBy = userId,
|
|
|
|
|
CreateTime = dt,
|
|
|
|
|
RecvUsers = info.Main.RecvUserInfoList == null ? null : string.Join(",", info.Main.RecvUserInfoList.Select(x => x.RecvUserName)),
|
|
|
|
|
TaskStatus = task.TaskStatus,
|
|
|
|
|
TaskType = task.TaskType
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await TenantDb.Insertable(taskList).ExecuteCommandAsync();
|
|
|
|
|
await TenantDb.Insertable(taskLogList).ExecuteCommandAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 创建关联子任务
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="task"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public async Task CreateSubTaskAsync(BusinessTask task)
|
|
|
|
|
{
|
|
|
|
|
var list = await GetSubRequestAsync(task.BusinessId, task.BusinessType.GetValueOrDefault());
|
|
|
|
|
await CreateSubTaskAsync(list.FindAll(x => x.TaskType != TaskBaseTypeEnum.NOT_SPECIFIED).OrderBy(x => x.TaskType));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 生成关联子任务
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="dataContext"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public async Task GenerateSubTaskAsync(TaskFlowDataContext dataContext)
|
|
|
|
|
{
|
|
|
|
|
ArgumentNullException.ThrowIfNull(dataContext, nameof(dataContext));
|
|
|
|
|
|
|
|
|
|
var task = dataContext.GetOrDefault<BusinessTask>(TaskFlowDataNameConst.BusinessTask);
|
|
|
|
|
if (task != null)
|
|
|
|
|
await CreateSubTaskAsync(task);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 获取提单确认信息
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="bsId">业务ID</param>
|
|
|
|
|
/// <param name="bsType">业务类型</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public async Task<DataResult<TaskGroup>> GetBLConfirmationAsync(long bsId, BusinessType bsType)
|
|
|
|
|
{
|
|
|
|
|
var confirmation = new TaskGroup
|
|
|
|
|
{
|
|
|
|
|
BusinessId = bsId,
|
|
|
|
|
BusinessType = bsType,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var mainTask = await TenantDb.Queryable<BusinessTask>()
|
|
|
|
|
.LeftJoin<SeaExport>((t, s) => t.BusinessId == s.Id)
|
|
|
|
|
.Where((t, s) => t.TaskType == BL_CONFIRM_TASK && t.BusinessId == bsId && t.BusinessType == bsType)
|
|
|
|
|
.OrderByDescending((t, s) => t.CreateTime)
|
|
|
|
|
.Select((t, s) => new BLTask
|
|
|
|
|
{
|
|
|
|
|
BLNumber = s.MBLNO,
|
|
|
|
|
Id = t.Id,
|
|
|
|
|
BusinessId = t.BusinessId,
|
|
|
|
|
BusinessType = t.BusinessType,
|
|
|
|
|
ParentBusinessId = t.ParentBusinessId,
|
|
|
|
|
ParentId = t.ParentId,
|
|
|
|
|
TaskType = t.TaskType,
|
|
|
|
|
TaskStatus = t.TaskStatus,
|
|
|
|
|
RecvUsers = t.RecvUsers,
|
|
|
|
|
IsMainBL = true,
|
|
|
|
|
Steps = SqlFunc.Subqueryable<TaskStep>().Where(ts => ts.TaskId == t.Id).ToList(ts => new TaskStepDto
|
|
|
|
|
{
|
|
|
|
|
}, true)
|
|
|
|
|
}).FirstAsync();
|
|
|
|
|
|
|
|
|
|
if (mainTask == null)
|
|
|
|
|
return DataResult<TaskGroup>.Success(confirmation);
|
|
|
|
|
|
|
|
|
|
var list = await TenantDb.Queryable<BusinessTask>()
|
|
|
|
|
.LeftJoin<SeaExportBillManage>((t, b) => t.BusinessId == b.Id)
|
|
|
|
|
.Where((t, b) => t.ParentBusinessId == bsId && t.BusinessType == bsType)
|
|
|
|
|
.Select((t, b) => new BLTask
|
|
|
|
|
{
|
|
|
|
|
BLNumber = b.HBLNO,
|
|
|
|
|
Id = t.Id,
|
|
|
|
|
BusinessId = t.BusinessId,
|
|
|
|
|
BusinessType = t.BusinessType,
|
|
|
|
|
ParentBusinessId = t.ParentBusinessId,
|
|
|
|
|
ParentId = t.ParentId,
|
|
|
|
|
TaskType = t.TaskType,
|
|
|
|
|
TaskStatus = t.TaskStatus,
|
|
|
|
|
RecvUsers = t.RecvUsers,
|
|
|
|
|
IsMainBL = false,
|
|
|
|
|
Steps = SqlFunc.Subqueryable<TaskStep>().Where(ts => ts.TaskId == t.Id).ToList(ts => new TaskStepDto
|
|
|
|
|
{
|
|
|
|
|
}, true)
|
|
|
|
|
}).ToListAsync();
|
|
|
|
|
|
|
|
|
|
list.Insert(0, mainTask);
|
|
|
|
|
confirmation.Tasks = list;
|
|
|
|
|
|
|
|
|
|
if (confirmation.Tasks.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
var ids = confirmation.Tasks.SelectMany(x => x.RecvUserIds).Distinct();
|
|
|
|
|
var userList = await Db.Queryable<SysUser>().Where(x => ids.Contains(x.Id))
|
|
|
|
|
.Select(x => new { x.Id, x.UserName }).ToListAsync();
|
|
|
|
|
|
|
|
|
|
foreach (var item in confirmation.Tasks)
|
|
|
|
|
{
|
|
|
|
|
item.RecvUserList = userList.Where(x => item.RecvUserIds.Contains(x.Id))
|
|
|
|
|
.Select(x => x.UserName).ToArray();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return DataResult<TaskGroup>.Success(confirmation);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 获取海运出口查询对象
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="expression">查询条件</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public ISugarQueryable<SeaExport> AsQueryable(Expression<Func<SeaExport, bool>>? expression = null)
|
|
|
|
|
{
|
|
|
|
|
return TenantDb.Queryable<SeaExport>().WhereIF(expression != null, expression);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override async Task<DataResult> OnTaskCreated(BusinessTask task)
|
|
|
|
|
{
|
|
|
|
|
if (task.TaskType == TaskBaseTypeEnum.WAIT_BOOKING)
|
|
|
|
|
{
|
|
|
|
|
var order = await AsQueryable(x => x.Id == task.BusinessId).Select(x => new
|
|
|
|
|
{
|
|
|
|
|
x.BookingNo,
|
|
|
|
|
x.MBLNO
|
|
|
|
|
}).FirstAsync();
|
|
|
|
|
|
|
|
|
|
//订单已有订舱编号或提单号,当前任务直接完成
|
|
|
|
|
if (order != null && (!order.BookingNo.IsNullOrEmpty() || !order.MBLNO.IsNullOrEmpty()))
|
|
|
|
|
{
|
|
|
|
|
string taskDesc = "因订单已存在";
|
|
|
|
|
if (!order.BookingNo.IsNullOrEmpty())
|
|
|
|
|
{
|
|
|
|
|
taskDesc += $"订舱编号({order.BookingNo})、";
|
|
|
|
|
}
|
|
|
|
|
if (!order.MBLNO.IsNullOrEmpty())
|
|
|
|
|
{
|
|
|
|
|
taskDesc += $"提单号({order.MBLNO})、";
|
|
|
|
|
}
|
|
|
|
|
taskDesc = taskDesc.Remove(taskDesc.Length - 1) + "而跳过订舱";
|
|
|
|
|
|
|
|
|
|
await SetTaskStatusAsync(new TaskUpdateRequest
|
|
|
|
|
{
|
|
|
|
|
BusinessId = task.BusinessId,
|
|
|
|
|
BusinessType = task.BusinessType,
|
|
|
|
|
TaskTypeName = task.TaskType.ToString(),
|
|
|
|
|
TaskStatus = TaskStatusEnum.Complete,
|
|
|
|
|
TaskDesc = taskDesc
|
|
|
|
|
}, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return DataResult.Success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal async Task<List<TaskCreationRequest>> GetSubRequestAsync(long id, BusinessType businessType, params long[]? recvUsers)
|
|
|
|
|
{
|
|
|
|
|
var svcList = await GetServicesAsync(id);
|
|
|
|
|
var list = new List<TaskCreationRequest>(svcList.Length);
|
|
|
|
|
for (int i = 0; i < svcList.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var dto = new TaskCreationRequest { BusinessId = id, BusinessType = businessType, RecvUserIdList = recvUsers };
|
|
|
|
|
var svcName = svcList[i];
|
|
|
|
|
|
|
|
|
|
if (svcName == "报关")
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_BAOGUAN.ToString();
|
|
|
|
|
else if (svcName == "订舱")
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_CANGDAN.ToString();
|
|
|
|
|
else if (svcName.Equals("VGM", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_VGM.ToString();
|
|
|
|
|
else if (svcName.Equals("AFR", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_AFR.ToString();
|
|
|
|
|
else if (svcName.Equals("AMS", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_AMS.ToString();
|
|
|
|
|
else if (svcName.Equals("ISF", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_ISF.ToString();
|
|
|
|
|
else if (svcName == "熏蒸")
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_XUNZHENG.ToString();
|
|
|
|
|
else if (svcName == "商检")
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_SHANGJIAN.ToString();
|
|
|
|
|
else if (svcName.Equals("COA证书", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_COA.ToString();
|
|
|
|
|
else if (svcName == "产地证")
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_CHANDIZHENG.ToString();
|
|
|
|
|
else if (svcName == "拖车")
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_TUOCHE.ToString();
|
|
|
|
|
else if (svcName == "保险")
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_INSURANCE.ToString();
|
|
|
|
|
else if (svcName == "出号")
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_CHUHAO.ToString();
|
|
|
|
|
else if (svcName == "内点入货")
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_NDRH.ToString();
|
|
|
|
|
else if (svcName == "快递")
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_DELIVERY.ToString();
|
|
|
|
|
else if (svcName == "箱使")
|
|
|
|
|
dto.TaskTypeName = TaskBaseTypeEnum.WAIT_XIANGSHI.ToString();
|
|
|
|
|
|
|
|
|
|
list.Add(dto);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal async Task<string[]> GetServicesAsync(long id)
|
|
|
|
|
{
|
|
|
|
|
var result = await djyService.Value.GetServiceProjectList(
|
|
|
|
|
new EmbedQueryServiceProjectWithStatus { BusinessId = id.ToString() });
|
|
|
|
|
|
|
|
|
|
if (!result.success)
|
|
|
|
|
return [];
|
|
|
|
|
|
|
|
|
|
if (((JObject)result.data)["ext"] is not JArray extArray || extArray.Count == 0)
|
|
|
|
|
return [];
|
|
|
|
|
|
|
|
|
|
return extArray.Where(x => x["isYield"].Value<bool>()).Select(
|
|
|
|
|
x => x["projectName"]?.Value<string>() ?? string.Empty).ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|