using Hotline.Orders;
using Hotline.Share.Dtos;
using Hotline.Share.Dtos.Article;
using Hotline.Share.Dtos.Snapshot;
using Hotline.Share.Tools;
using Hotline.Snapshot;
using SqlSugar;
using XF.Domain.Authentications;
using XF.Domain.Dependency;
using XF.Domain.Repository;
using Hotline.Repository.SqlSugar.Extensions;
using Hotline.Share.Enums;
using Hotline.Share.Enums.Order;
using Hotline.Share.Requests;
using Hotline.Share.Enums.Snapshot;
using Mapster;
using Hotline.Caching.Interfaces;
using NPOI.Util.ArrayExtensions;
using XF.Domain.Exceptions;
using Hotline.Settings;
using Hotline.Share.Dtos.Settings;
using Hotline.File;
using Hotline.Share.Enums.Article;
using Hotline.Tools;
using DotNetCore.CAP;
using Microsoft.AspNetCore.Http;
using Hotline.Share.Dtos.FlowEngine;
using Hotline.FlowEngine.Workflows;
using Hotline.Share.Enums.FlowEngine;
using Hotline.Share.Dtos.Order;
using Hotline.Share.Mq;
using Hotline.Snapshot.Notifications;
using Hotline.EventBus;
using Hotline.Quality.Notifications;
using XF.Utility.EnumExtensions;
using Hotline.ThirdAccountDomainServices.Interfaces;
using Hotline.Snapshot.IRepository;
using Hotline.Share.Enums.CallCenter;
using Hotline.Repository.SqlSugar.Snapshot;
using Hotline.Settings.Hotspots;
using Microsoft.Extensions.Options;
using XF.Utility.MQ;
using System.Threading;
using BulletinOutDto = Hotline.Share.Dtos.Article.BulletinOutDto;
using Hotline.Article;
using NPOI.SS.Formula.Functions;

namespace Hotline.Application.Snapshot;

/// <summary>
/// 随手拍应用层
/// </summary>
public abstract class SnapshotApplicationBase
{
    private readonly IThirdAccountRepository _thirdAccountRepository;
    private readonly IRepository<Order> _orderRepository;
    private readonly ISnapshotBulletinRepository _bulletinRepository;

    /// <summary>
    /// 行业
    /// </summary>
    private readonly IIndustryRepository _industryRepository;
    private readonly IThirdIdentiyService _thirdLoginService;
    private readonly ISessionContext _sessionContext;
    private readonly IRedPackRecordRepository _redPackRecordRepository;
    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
    private readonly ISystemSettingCacheManager _sysSetting;
    private readonly ISystemAreaDomainService _systemAreaDomainService;
    private readonly IRepository<SystemArea> _systemAreaRepository;
    private readonly IFileRepository _fileRepository;
    private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
    private readonly ISnapshotOrderPublishRepository _snapshotOrderPublishRepository;
    private readonly IRepository<WorkflowTrace> _workflowTraceRepository;
    private readonly IPractitionerRepository _practitionerRepository;
    private readonly IVolunteerRepository _volunteerRepository;
    private readonly IVolunteerReportRepository _volunteerReportRepository;
    private readonly ISystemLogRepository _systemLog;
    private readonly IGuiderSystemService _guiderSystemService;
    private readonly ICapPublisher _capPublisher;
    private readonly Publisher _publisher;
    private readonly IFileDomainService _fileDomainService;
    private readonly ICommunityInfoRepository _communityInfoRepository;
    private readonly IRedPackAuditRepository _redPackAuditRepository;
    private readonly IRedPackGuiderAuditRepository _redPackGuiderAuditRepository;
    private readonly IOrderVisitRepository _orderVisitRepository;
    private readonly IOrderVisitDetailRepository _orderVisitDetailRepository;
    private readonly IInviteCodeRecordRepository _inviteCodeRecordRepository;
    private readonly IInviteCodeRepository _inviteCodeRepository;
    private readonly ICitizenRepository _citizenRepository;
    private readonly ISnapshotPointsRecordRepository _pointsRecordRepository;
    private readonly IOptionsSnapshot<MqConfiguration> _mqConfiguration;
    private readonly INotificationReceiverRepository _notificationReceiverRepository;
    private readonly INotificationRepository _notificationRepository;

    public SnapshotApplicationBase(IThirdIdentiyService thirdLoginService, IIndustryRepository industryRepository, ISnapshotBulletinRepository bulletinRepository, ISessionContext sessionContext, IRedPackRecordRepository redPackRecordRepository, IRepository<Order> orderRepository, IThirdAccountRepository thirdAccountRepository, IOrderSnapshotRepository orderSnapshotRepository, ISystemSettingCacheManager systemSettingCacheManager, ISystemAreaDomainService systemAreaDomainService, IFileRepository fileRepository, ISystemDicDataCacheManager systemDicDataCacheManager, ISnapshotOrderPublishRepository snapshotOrderPublishRepository, IRepository<WorkflowTrace> workflowTraceRepository, IPractitionerRepository practitionerRepository, IRepository<SystemArea> systemAreaRepository, IVolunteerRepository volunteerRepository, IVolunteerReportRepository volunteerReportRepository, ISystemLogRepository systemLog, IGuiderSystemService guiderSystemService, ICapPublisher capPublisher, Publisher publisher, IFileDomainService fileDomainService, ICommunityInfoRepository communityInfoRepository, IRedPackAuditRepository redPackAuditRepository, IOrderVisitRepository orderVisitRepository, IOrderVisitDetailRepository orderVisitDetailRepository, IRedPackGuiderAuditRepository redPackGuiderAuditRepository, IInviteCodeRecordRepository inviteCodeRecordRepository, IInviteCodeRepository inviteCodeRepository, ICitizenRepository citizenRepository, ISnapshotPointsRecordRepository snapshotPointsRecordRepository, IOptionsSnapshot<MqConfiguration> mqConfiguration, INotificationReceiverRepository notificationReceiverRepository, INotificationRepository notificationRepository)
    {
        _thirdLoginService = thirdLoginService;
        _industryRepository = industryRepository;
        _bulletinRepository = bulletinRepository;
        _sessionContext = sessionContext;
        _redPackRecordRepository = redPackRecordRepository;
        _orderRepository = orderRepository;
        _thirdAccountRepository = thirdAccountRepository;
        _orderSnapshotRepository = orderSnapshotRepository;
        _sysSetting = systemSettingCacheManager;
        _systemAreaDomainService = systemAreaDomainService;
        _fileRepository = fileRepository;
        _systemDicDataCacheManager = systemDicDataCacheManager;
        _snapshotOrderPublishRepository = snapshotOrderPublishRepository;
        _workflowTraceRepository = workflowTraceRepository;
        _practitionerRepository = practitionerRepository;
        _systemAreaRepository = systemAreaRepository;
        _volunteerRepository = volunteerRepository;
        _volunteerReportRepository = volunteerReportRepository;
        _systemLog = systemLog;
        _guiderSystemService = guiderSystemService;
        _capPublisher = capPublisher;
        _publisher = publisher;
        _fileDomainService = fileDomainService;
        _communityInfoRepository = communityInfoRepository;
        _redPackAuditRepository = redPackAuditRepository;
        _orderVisitRepository = orderVisitRepository;
        _orderVisitDetailRepository = orderVisitDetailRepository;
        _redPackGuiderAuditRepository = redPackGuiderAuditRepository;
        _inviteCodeRecordRepository = inviteCodeRecordRepository;
        _inviteCodeRepository = inviteCodeRepository;
        _citizenRepository = citizenRepository;
        _pointsRecordRepository = snapshotPointsRecordRepository;
        _mqConfiguration = mqConfiguration;
        _notificationReceiverRepository = notificationReceiverRepository;
        _notificationRepository = notificationRepository;
    }

    #region 小程序
    /// <summary>
    /// 获取随手拍小程序首页数据
    /// </summary>
    /// <returns></returns>
    public async Task<HomePageOutDto> GetHomePageAsync()
    {
        var fileServiceUrl = _sysSetting.FileServerUrl;
        var fileDownloadApi = fileServiceUrl + _sysSetting.FileDownloadApi;
        var items = await _industryRepository.Queryable()
            .Where(m => m.IsEnable)
            .OrderBy(m => m.DisplayOrder)
            .ToListAsync(m => new HomeIndustryOutDto());
        items.ForEach(m =>
        {
            if (m.BackgroundImgUrl.NotNullOrEmpty())
                m.BackgroundImgUrl = fileServiceUrl + m.BackgroundImgUrl;
            if (m.BannerImgUrl.NotNullOrEmpty())
                m.BannerImgUrl = fileServiceUrl + m.BannerImgUrl;
            if (m.CareCellImgUrl.NotNullOrEmpty())
                m.CareCellImgUrl = fileServiceUrl + m.CareCellImgUrl;
            if (m.CellImgUrl.NotNullOrEmpty())
                m.CellImgUrl = fileServiceUrl + m.CellImgUrl;
        });
        var banners = (await _bulletinRepository.Queryable()
            .Where(m => m.SnapshotBulletinTypeId == "SSPBanner" && m.BulletinState == EBulletinState.ReviewPass && m.IsArrive == true)
            .Select(m => m.Content).ToListAsync()).Select(m => m.GetHtmlImgSrc()).ToList();

        return new HomePageOutDto
        {
            Banners = banners,
            Industrys = items
        };
    }

    /// <summary>
    /// 获取小程序首页弹窗
    /// </summary>
    public async Task<BulletinOutDto> GetBulletionPopupAsync(CancellationToken requestAborted)
    {
        var item = await _bulletinRepository.Queryable()
            .Where(m => m.BulletinState == EBulletinState.ReviewPass)
            .Where(m => m.BulletinTime >= DateTime.Now && m.IsPopup == true)
            .OrderByDescending(m => m.CreationTime)
            .Select<BulletinOutDto>()
            .FirstAsync(requestAborted);
        return item;
    }

    /// <summary>
    /// 获取行业集合
    /// </summary>
    /// <returns></returns>
    public async Task<IList<IndustryOutDto>> GetIndustresAsync()
    {
        var fileServiceUrl = _sysSetting.FileServerUrl;
        var fileDownloadApi = fileServiceUrl + _sysSetting.FileDownloadApi;
        var items = await _industryRepository.Queryable()
            .Where(m => m.IsEnable)
            .OrderBy(m => m.DisplayOrder)
            .ToListAsync(m => new IndustryOutDto());
        return items;
    }

    /// <summary>
    /// 行业页面基础数据
    /// </summary>
    /// <param name="id"></param>
    /// <param name="requestAborted"></param>
    /// <returns></returns>
    public async Task<IndustryBaseOutDto> GetIndustryBaseAsync(string id, CancellationToken requestAborted)
    {
        var fileServiceUrl = _sysSetting.FileServerUrl;
        var fileDownloadApi = fileServiceUrl + _sysSetting.FileDownloadApi;
        var indurstry = await _industryRepository.GetAsync(id, requestAborted)
            ?? throw UserFriendlyException.SameMessage("行业不存在:" + id);

        var bulletinId = await _bulletinRepository.Queryable()
            .Where(m => m.SnapshotBulletinTypeId == indurstry.BulletinTypeGuideId && m.BulletinState == EBulletinState.ReviewPass)
            .OrderByDescending(m => m.CreationTime)
            .Select(m => m.Id)
            .FirstAsync(requestAborted);
        var outDto = new IndustryBaseOutDto
        {
            Industry = indurstry.Adapt<IndustryOutDto>()
        };
        outDto.BulletinId = bulletinId;
        if (indurstry.IndustryType == EIndustryType.Declare)
        {
            outDto.AreaTree = (await _systemAreaDomainService.GetAreaTree(parentId: "510300")).Adapt<List<SystemAreaOutDto>>();
            outDto.AreaTree.ToList().ForEach(m =>
            {
                if (m.Children.IsNullOrEmpty())
                    m.Children = new List<SystemAreaOutDto>() { new SystemAreaOutDto { Id = "0", AreaName = "无" } };
            });
            outDto.Files = (await _fileRepository.GetByKeyAsync(indurstry.Id, requestAborted)).Adapt<List<IndustryFileDto>>();
            outDto.Files.ToList().ForEach(m => m.Url = fileDownloadApi + m.AdditionId);
            outDto.WorkplaceName = _systemDicDataCacheManager.WorkplaceName;
            outDto.Workplace = _systemDicDataCacheManager.Workplace;
            outDto.JobType = _systemDicDataCacheManager.JobType;
            outDto.BusinessUnitType = _systemDicDataCacheManager.BusinessUnitType;
        }
        return outDto;
    }


    /// <summary>
    /// 获取公开工单集合
    /// </summary>
    /// <param name="dto"></param>
    /// <param name="requestAborted"></param>
    /// <returns></returns>
    public async Task<IList<OrderPublishOutDto>> GetOrderPublishAsync(OrderPublishInDto dto, CancellationToken requestAborted)
    {
        dto.ValidateObject();
        var items = await _snapshotOrderPublishRepository.Queryable()
            .LeftJoin<Order>((s, o) => s.Id == o.Id)
            .Where((s, o) => s.IndustryId == dto.IndustryId && s.Status == EOrderSnapshotPublishStatus.Agree)
            .WhereIF(dto.Keyword.NotNullOrEmpty(), (s, o) => s.No.Contains(dto.Keyword) || s.ArrangeTitle.Contains(dto.Keyword))
            .Select<OrderPublishOutDto>((s, o) => new OrderPublishOutDto
            {
                Title = s.ArrangeTitle
            }, true)
            .ToFixedListAsync(dto, requestAborted);

        return items;
    }

    /// <summary>
    /// 获取公开的工单详情
    /// </summary>
    /// <param name="id"></param>
    /// <param name="requestAborted"></param>
    /// <returns></returns>
    public async Task<OrderPublishDetailOutDto> GetOrderPublishDetailAsync(string id, CancellationToken requestAborted)
    {
        var order = await _orderRepository.GetAsync(id) ??
            throw UserFriendlyException.SameMessage("工单不存在");

        var outDto = order.Adapt<OrderPublishDetailOutDto>();
        var fileServiceUrl = _sysSetting.FileServerUrl;
        if (outDto.FileJson != null)
        {
            foreach (var item in outDto.FileJson)
            {
                item.Path = fileServiceUrl + item.Path;
            }
        }
        var traces = await _workflowTraceRepository.Queryable()
            .Where(m => m.ExternalId == order.Id && m.Status == EWorkflowStepStatus.Handled)
            .OrderBy(m => m.AcceptTime)
            .ToListAsync(requestAborted);
        var centre = traces.Where(m => m.StepType == EStepType.End || m.StepType == EStepType.Start || m.BusinessType == EBusinessType.Send || m.BusinessType == EBusinessType.Seat || m.BusinessType == EBusinessType.File)
            .Select(m => new SnapshotWorkflow(m.Id, m.Name, m.HandleTime.Value))
            .ToList();
        outDto.Workflow = traces.Where(m => !centre.Select(s => s.Id).ToList().Contains(m.Id))
            .Select(m => new SnapshotWorkflow(m.Id, m.Name, m.HandleTime.Value))
            .ToList();
        outDto.Workflow.AddRange(centre);
        outDto.Workflow = outDto.Workflow.OrderBy(m => m.HandleTime).ToList();
        return outDto;
    }

    /// <summary>
    /// 获取随手拍小程序公告
    /// </summary>
    /// <returns></returns>
    public async Task<IReadOnlyList<BulletinListOutDto>> GetBulletinsAsync(BulletinInDto dto, CancellationToken cancellationToken)
    {
        var items = await _bulletinRepository.Queryable()
            .Where(m => m.BulletinState == EBulletinState.ReviewPass && m.IsArrive == true)
            .LeftJoin<Industry>((bulletin, industry) => bulletin.SnapshotBulletinTypeId == industry.BulletinTypePublicityId)
            .Where((bulletin, industry) => industry.Id == dto.IndustryId)
            .ToFixedListAsync(dto, cancellationToken);

        return items.Adapt<IReadOnlyList<BulletinListOutDto>>();
    }

    /// <summary>
    /// 获取个人中心数据
    /// </summary>
    /// <returns></returns>
    public async Task<SnapshotUserInfoOutDto> GetSnapshotUserInfoAsync(CancellationToken token)
    {
        var userInfo = await _citizenRepository.GetAsync(_sessionContext.UserId);

        var dayTime = DateTime.Now;
        var query = _redPackRecordRepository.Queryable()
            .Where(m => m.WXOpenId == _sessionContext.OpenId && m.PickupStatus == ERedPackPickupStatus.Received)
            .Where(m => m.CreationTime.Date == dayTime.Date)
            .Select(m => SqlFunc.AggregateSum(m.Amount));
#if DEBUG
        var sql = query.ToSqlString();
#endif
        var redPack = await query.FirstAsync(token);

        var outDto = await _orderRepository.Queryable()
            .Where(m => m.Contact == userInfo.PhoneNumber && m.CreatorId == _sessionContext.UserId)
            .Select(m => new SnapshotUserInfoOutDto
            {
                NoReplyCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.Status < EOrderStatus.Filed, 1, 0)),
                ReplyCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.Status >= EOrderStatus.Filed, 1, 0)),
                AppraiseCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.Status == EOrderStatus.Visited, 1, 0)),
            }).FirstAsync(token);

        outDto.DayAmount = redPack;
        outDto.TotalAmount = await _redPackRecordRepository.GetReceivedTotalAmountAsync(_sessionContext.OpenId, token);
        outDto.PhoneNumber = userInfo.PhoneNumber;
        return outDto;
    }

    /// <summary>
    /// 获取工单列表
    /// </summary>
    public async Task<IList<OrderOutDto>> GetSnapshotOrdersAsync(OrderInDto dto, CancellationToken cancellationToken)
    {
        var items = await _orderSnapshotRepository.Queryable()
            .LeftJoin<Order>((snapshot, order) => snapshot.Id == order.Id)
            .Where((snapshot, order) => order.Contact == _sessionContext.Phone)
            .WhereIF(dto.Status == EOrderQueryStatus.Appraise, (snapshot, order) => order.Status == EOrderStatus.Visited)
            .WhereIF(dto.Status == EOrderQueryStatus.NoReply, (snapshot, order) => order.Status < EOrderStatus.Filed)
            .WhereIF(dto.Status == EOrderQueryStatus.Reply, (snapshot, order) => order.Status >= EOrderStatus.Filed)
            .WhereIF(dto.KeyWords.NotNullOrEmpty(), (snapshot, order) => order.Title.Contains(dto.KeyWords) || order.No.Contains(dto.KeyWords))
            .OrderByDescending((snapshot, order) => snapshot.CreationTime)
            .Select((snapshot, order) => new OrderOutDto
            {
                Id = snapshot.Id,
                OrderNo = order.No,
                Title = order.Title,
                Status = order.Status,
                IndustryName = snapshot.IndustryName,
                CreationTime = order.CreationTime,
                Area = order.City
            })
            .ToFixedListAsync(dto, cancellationToken);

        return items;
    }

    /// <summary>
    /// 积分详情集合
    /// </summary>
    /// <param name="dto"></param>
    /// <returns></returns>
    public async Task<IList<PointItemsOutDto>> GetPointItemsAsync(PointItemsInDto dto, CancellationToken token)
    {
        var items = await _pointsRecordRepository.Queryable()
            .Where(m => m.UserId == _sessionContext.UserId && dto.Direction == m.Direction)
            .Select(m => new PointItemsOutDto(), true)
            .OrderByDescending(m => m.CreationTime)
            .ToFixedListAsync(dto, token);
        return items;
    }

    /// <summary>
    /// 积分总计
    /// </summary>
    /// <param name="dto"></param>
    /// <param name="requestAborted"></param>
    /// <returns></returns>
    public async Task<int> GetPointsTotalAsync(PointItemsInDto dto, CancellationToken token)
    {
        return await _pointsRecordRepository.Queryable()
            .Where(m => m.UserId == _sessionContext.UserId && dto.Direction == m.Direction)
            .SumAsync(m => m.Points);
    }

    public async Task<IList<GetNotifyOutDto>> GetNotificationAsync(GetNotifyInDto dto, CancellationToken requestAborted)
    {
        var items = await _notificationReceiverRepository.Queryable()
            .LeftJoin<Notification>((m , notify) => m.NotificationId == notify.Id)
            .Where((m, notify) => m.ReceiverId == _sessionContext.UserId && dto.NotifyType == notify.NotifyType)
            .Select((m, notify) => new GetNotifyOutDto 
            {
                NotificationId = m.NotificationId,
                Title = notify.Title ,
                CreationTime = m.CreationTime,
                IsRead = m.IsRead
            }, true)
            .ToFixedListAsync(dto, requestAborted);
        return items;
    }

    /// <summary>
    /// 获取通知详情
    /// </summary>
    /// <param name="id"></param>
    /// <param name="requestAborted"></param>
    /// <returns></returns>
    public async Task<BulletinOutDto> GetNotificationDetailsAsync(string id, CancellationToken token)
    {
        var outDto = await _notificationRepository.Queryable()
            .LeftJoin<SnapshotBulletin>((notify, bulletin) => notify.ExternalId == bulletin.Id)
            .Where((notify, bulletin) => notify.Id == id)
            .Select((notify, bulletin) => new BulletinOutDto
            {
                Id = bulletin.Id,
                Title = bulletin.Title,
                CreationTime = bulletin.CreationTime,
                Content = bulletin.Content,
                VideoPath = bulletin.VideoPath
            })
            .FirstAsync(token);
        await _notificationReceiverRepository.Updateable()
            .SetColumns(m => m.IsRead, true)
            .Where(m => m.NotificationId == id && m.ReceiverId == _sessionContext.UserId)
            .ExecuteCommandAsync(token);

        return outDto;
    }

    /// <summary>
    /// 获取工单详情
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public async Task<OrderPublishDetailOutDto> GetSnapshotOrderDetailAsync(string id, CancellationToken cancellationToken)
    {
        var order = await _orderRepository.GetAsync(id) ??
            throw UserFriendlyException.SameMessage("工单不存在");

        var outDto = order.Adapt<OrderPublishDetailOutDto>();
        var fileServiceUrl = _sysSetting.FileServerUrl;
        if (outDto.FileJson != null)
        {
            foreach (var item in outDto.FileJson)
            {
                item.Path = fileServiceUrl + item.Path;
            }
        }

        await _orderVisitRepository.Queryable()
            .Where(m => m.OrderId == id).FirstAsync(cancellationToken)
            .Then(orderVisit =>
            {
                if (orderVisit.NowEvaluate != null)
                {
                    outDto.IsVisit = true;
                }
            });

        await _redPackAuditRepository.Queryable()
            .Where(m => m.OrderId == id)
            .FirstAsync(cancellationToken)
            .Then(redPack =>
            {
                outDto.AuditRemark = redPack.AuditRemark;
                outDto.RedPackStatus = redPack.Status;
            });

        var traces = await _workflowTraceRepository.Queryable()
            .Where(m => m.ExternalId == order.Id && m.Status == EWorkflowStepStatus.Handled)
            .OrderBy(m => m.AcceptTime)
            .ToListAsync(cancellationToken);
        var centre = traces.Where(m => m.StepType == EStepType.End || m.StepType == EStepType.Start || m.BusinessType == EBusinessType.Send || m.BusinessType == EBusinessType.Seat || m.BusinessType == EBusinessType.File)
            .Select(m => new SnapshotWorkflow(m.Id, m.Name, m.HandleTime.Value))
            .ToList();
        outDto.Workflow = traces.Where(m => !centre.Select(s => s.Id).ToList().Contains(m.Id))
            .Select(m => new SnapshotWorkflow(m.Id, m.Name, m.HandleTime.Value))
            .ToList();
        outDto.Workflow.AddRange(centre);
        outDto.Workflow = outDto.Workflow.OrderBy(m => m.HandleTime).ToList();
        return outDto;
    }

    /// <summary>
    /// 获取回访详情
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public async Task<IList<OrderVisitItemsOutDto>> GetOrderVisitDetailAsync(string id)
    {
        var orderVisitId = await _orderVisitRepository.Queryable()
            .Where(m => m.OrderId == id)
            .Select(m => m.Id)
            .FirstAsync();
        if (orderVisitId.IsNullOrEmpty()) return [];
        var orderVisitDetail = await _orderVisitDetailRepository.Queryable()
            .Where(m => m.VisitId == orderVisitId)
            .ToListAsync();
        if (orderVisitDetail.IsNullOrEmpty()) return [];
        var seat = orderVisitDetail.Where(m => m.VisitTarget == EVisitTarget.Seat).First();
        var item = new OrderVisitItemsOutDto();
        if (seat != null && seat.SeatEvaluate.HasValue)
        {
            item.SeatEvaluate = seat.SeatEvaluate.Value.GetDescription();
        }
        var org = orderVisitDetail.Where(m => m.VisitTarget == EVisitTarget.Org).First();
        if (org != null)
        {
            if (org.OrgProcessingResults != null) item.OrgProcessingResults = org.OrgProcessingResults.Value;
            if (org.OrgHandledAttitude != null) item.OrgHandledAttitude = org.OrgHandledAttitude.Value;
        }
        return
        [
            item
        ];
    }

    /// <summary>
    /// 获取当月详细红包列表
    /// </summary>
    /// <param name="dto"></param>
    /// <returns></returns>
    public async Task<IList<RedPackOutDto>> GetRedPacksAsync(RedPacksInDto dto, CancellationToken cancellationToken)
    {
        var items = await _redPackRecordRepository.Queryable(includeDeleted: true)
            .LeftJoin<Order>((m, order) => m.OrderId == order.Id)
            .Where(m => m.IsDeleted == false)
            .Where(m => m.WXOpenId == _sessionContext.OpenId)
            .WhereIF(dto.Status == ERedPackPickupStatus.Unreceived, (m, order) => m.PickupStatus == dto.Status && (order.HotspotId == null || order.HotspotId.StartsWith("18") == false))
            .WhereIF(dto.Status != ERedPackPickupStatus.Unreceived, (m, order) => m.PickupStatus == dto.Status)
            .Where(m => m.CreationTime.ToString("yyyy-MM") == dto.Time)
            .Select((m, order) => new RedPackOutDto
            {
                OrderId = order.Id,
                Amount = m.Amount,
                Title = order.Title,
                CreationTime = m.CreationTime,
                RedPackAuditId = m.RedPackAuditId
            })
            .ToFixedListAsync(dto, cancellationToken);

        return items;
    }

    /// <summary>
    /// 获取用户领取过的红包总金额
    /// </summary>
    /// <returns></returns>
    public async Task<string> GetRedPackReceivedTotalAsync(CancellationToken cancellationToken)
    {
        return (await _redPackRecordRepository.GetReceivedTotalAmountAsync(_sessionContext.OpenId, cancellationToken)).ToYuanFinance();
    }

    /// <summary>
    /// 按月统计红包金额
    /// </summary>
    /// <param name="count"></param>
    /// <returns></returns>
    public async Task<IReadOnlyList<RedPackDateOutDto>> GetRedPackDateAsync(RedPackDateInDto dto, CancellationToken cancellationToken)
    {
        var openId = _sessionContext.OpenId;
        var item = await _redPackRecordRepository.Queryable()
            .Where(m => m.WXOpenId == openId || m.PhoneNumber == _sessionContext.Phone)
            .Where(m => m.PickupStatus == dto.Status)
            .GroupBy(m => m.CreationTime.ToString("yyyy-MM"))
            .OrderByDescending(m => m.CreationTime)
            .Select(m => new RedPackDateOutDto
            {
                CreationTime = SqlFunc.AggregateMax(m.CreationTime.Date),
                Amount = SqlFunc.AggregateSum(m.Amount)
            })
            .ToListAsync(cancellationToken);

        return item;
    }

    /// <summary>
    /// 获取随手拍公告详情
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public async Task<BulletinOutDto> GetBulletinsDetailAsync(string id)
    {
        var detail = await _bulletinRepository.Queryable()
            .Where(m => m.Id == id)
            .Where(m => m.BulletinState == Share.Enums.Article.EBulletinState.ReviewPass)
            .Select(m => new BulletinOutDto
            {
                Id = m.Id,
                Title = m.Title,
                Content = m.Content,
                CreationTime = m.CreationTime
            })
            .FirstAsync();
        return detail;
    }

    /// <summary>
    /// 保存用户自己的邀请码
    /// </summary>
    /// <param name="dto"></param>
    /// <returns></returns>
    public async Task SaveInvitationCodeAsync(SaveInvitationCodeInDto dto)
    {
        dto.ValidateObject();
        if (int.TryParse(dto.InvitationCode, out var invitationCode) == false)
        {
            throw new UserFriendlyException(200, "邀请码格式错误");
        }
        var userInfo = await _citizenRepository.GetAsync(_sessionContext.UserId)
            ?? throw UserFriendlyException.SameMessage("用户不存在");
        if (userInfo.InvitationCode.NotNullOrEmpty())
        {
            throw new UserFriendlyException(200, "邀请码已存在");
        }
        if (_inviteCodeRecordRepository.Queryable().Where(m => m.InviteCode == dto.InvitationCode).Any())
        {
            throw new UserFriendlyException(200, "邀请码已经被使用");
        }
        userInfo.InvitationCode = dto.InvitationCode;
        var invite = await _inviteCodeRepository.Queryable()
            .Where(m => invitationCode >= m.BeginCode && invitationCode <= m.EndCode)
            .FirstAsync() ?? throw new UserFriendlyException(200, "邀请码无效");
        var entity = new InviteCodeRecord
        {
            OrgId = invite.Id,
            OrgName = invite.OrgName,
            InviteCode = dto.InvitationCode,
            WXOpenId = _sessionContext.OpenId,
            PhoneNumber = userInfo.PhoneNumber,
            Name = userInfo.Name,
        };
        await _citizenRepository.UpdateAsync(userInfo);
        await _inviteCodeRecordRepository.AddAsync(entity);
    }

    #endregion

    #region 网格员
    /// <summary>
    /// 推送工单到网格员系统
    /// </summary>
    /// <param name="orderId"></param>
    /// <returns></returns>
    public async Task PostOrderGuiderSystemAsync(string orderId, CancellationToken cancellationToken)
    {
        string LogName = "推送网格员系统";
        var order = await _orderRepository.GetAsync(orderId, cancellationToken);
        var orderSnapshot = await _orderSnapshotRepository.GetAsync(orderId, cancellationToken);
        if (order is null || orderSnapshot is null)
        {
            var msg = order is null ? "order" : "orderSnapshot";
            _systemLog.Add(LogName, $"OrderId: {orderId}", $"根据Id查询{msg}为null");
            return;
        }
        orderSnapshot.DeadLine = DateTime.Now.AddHours(_sysSetting.OvertimeBack);
        orderSnapshot.SendGuidSystemTime = DateTime.Now;
        var keySecret = _sysSetting.TianQueAppKeySecret.Split('|');

        var token = new ThirdTokenDto
        {
            AppId = keySecret[0],
            Secret = keySecret[1]
        };
        if (order.FileJson.NotNullOrEmpty())
        {
            var url = _sysSetting.FileServerUrl;
            order.FileJson.ForEach(m => { m.Path = url + m.Path; });
        }
        var result = await _guiderSystemService.PostOrder(order, orderSnapshot, token);
        orderSnapshot.GuiderAccLog = result.Result.ToJson();
        if (result.Code != 0)
        {
            _systemLog.Add(LogName, $"OrderNo: {order.No}", $"推送失败: {result.ToJson()}");
            orderSnapshot.GuiderAccLog = result.ToJson();
        }
        orderSnapshot.NetworkENumber = result.Result.GuiderSystemId;
        await _orderSnapshotRepository.UpdateAsync(orderSnapshot, cancellationToken);
        _systemLog.Add(LogName, $"OrderNo: {order.No}", status: 1);
        if (result.Code == 0)
        {
            double intervalTime = await _industryRepository.GetIntervalTimeAsync(orderSnapshot.IndustryId, _sysSetting.OvertimeBack, cancellationToken);
            _systemLog.Add(LogName, $"OrderNo: {order.No}", $"intervalTime: {intervalTime} overtimeBack: {_sysSetting.OvertimeBack}");
            await _capPublisher.PublishDelayAsync(TimeSpan.FromHours(intervalTime), EventNames.GuiderSystemReplyDelay, new PostGuiderSystemDelayed(order.Id), cancellationToken: cancellationToken);
        }
    }

    /// <summary>
    /// 延迟检查网格员是否回复工单
    /// </summary>
    /// <param name="orderId"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task GuiderSystemReplyDelayAsync(string orderId, CancellationToken cancellationToken)
    {
        var orderSnapshot = await _orderSnapshotRepository.GetAsync(orderId)
            ?? throw new UserFriendlyException($"orderId:{orderId} order_snapshot {_mqConfiguration?.Value?.RabbitMq?.VirtualHost} 不存在该数据");
        if (orderSnapshot.IsDeal != null && orderSnapshot.IsDeal == true)
            return;
        // 网格员未回复, 推送事件
        await _publisher.PublishAsync(new GuiderSystemTimeOutBackNotification(orderId), cancellationToken);
    }

    /// <summary>
    /// 网格员系统回复
    /// </summary>
    /// <param name="dto"></param>
    /// <returns></returns>
    public async Task SaveGuiderSystemReplyAsync(GuiderSystemInDto dto, CancellationToken token)
    {
        var orderSnapshot = await _orderSnapshotRepository.GetByNetworkENumberAsync(dto.AppealNumber)
            ?? throw UserFriendlyException.SameMessage("工单不存在");
        dto.Adapt(orderSnapshot);
        if (dto.ReplyFileList.NotNullOrEmpty())
        {
            foreach (var file in dto.ReplyFileList)
            {
                await _fileDomainService.GetNetworkFileAsync(file, orderSnapshot.Id, token);
            }
        }
        orderSnapshot.IsGuidSystemCallBack = true;
        orderSnapshot.GuidSystemCallBackTime = DateTime.Now;
        await _orderSnapshotRepository.UpdateAsync(orderSnapshot);
        // 网格员办结
        if (orderSnapshot.ReplyResultType == EGuiderSystemReplyType.Field)
        {
            await _publisher.PublishAsync(new GuiderSystemFieldNotification(orderSnapshot, dto.Adapt<CommunityInfo>()), token);
        }
        // 网格员超时未回复退回
        if (orderSnapshot.ReplyResultType == EGuiderSystemReplyType.Returned)
        {
            await _publisher.PublishAsync(new GuiderSystemTimeOutBackNotification(orderSnapshot.Id), token);
        }
    }

    /// <summary>
    /// 根据网格员系统回复的内容同步网格员信息
    /// </summary>
    /// <param name="orderId"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task SyncGuiderInfoAsync(string orderId, CancellationToken cancellationToken)
    {
        var guiderInfo = await _orderSnapshotRepository.Queryable()
            .Where(m => m.Id == orderId)
            .Select(m => new { m.MemberName, m.MemberMobile })
            .FirstAsync(cancellationToken);
        var guider = await _citizenRepository.GetByPhoneNumberAsync(guiderInfo.MemberMobile);
        if (guider != null) return;

        var entity = new Citizen
        {
            Name = guiderInfo.MemberName,
            PhoneNumber = guiderInfo.MemberMobile,
            CitizenType = EReadPackUserType.Guider
        };
        entity.Id = await _citizenRepository.AddAsync(entity, cancellationToken);
        var third = (await _thirdAccountRepository.GetByPhoneNumberAsync(guiderInfo.MemberMobile, cancellationToken))
            .Where(m => m.ExternalId == null)
            .ToList();

        third.ForEach(m => m.ExternalId = entity.Id);
        await _thirdAccountRepository.UpdateRangeAsync(third);
    }

    /// <summary>
    /// 同步社区信息
    /// </summary>
    /// <param name="entity"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task SyncCommunityInfoAsync(CommunityInfo community, CancellationToken cancellationToken)
    {
        var entity = await _communityInfoRepository.GetAsync(community.Id, cancellationToken);
        if (entity == null)
        {
            community.UniqueKey = community.GetUniqueKey();
            await _communityInfoRepository.AddAsync(community);
            return;
        }
        if (entity.UniqueKey != community.GetUniqueKey())
        {
            await _communityInfoRepository.UpdateAsync(community, cancellationToken);
        }
    }
    #endregion

    #region 从业人员

    /// <summary>
    /// 添加从业人员
    /// </summary>
    /// <param name="dtos"></param>
    /// <returns></returns>
    public async Task AddPractitionerAsync(IList<AddBatchPractitionerInDto> dtos)
    {
        foreach (var item in dtos)
        {
            try
            {
                var entity = item.Adapt<Practitioner>();
                switch (item.Gender.Trim())
                {
                    case "男":
                        entity.Gender = EGender.Male;
                        break;
                    case "女":
                        entity.Gender = EGender.Female;
                        break;
                    default:
                        entity.Gender = EGender.Unknown;
                        break;
                }
                var area = await _systemAreaRepository.Queryable()
                    .Where(m => m.AreaName == item.AreaName)
                    .FirstAsync();
                entity.SystemAreaId = area.Id;
                entity.SystemAreaName = area.AreaName;
                await _practitionerRepository.AddAsync(entity);

            }
            catch (Exception e)
            {
                var msg = e.Message;
            }
        }
    }

    /// <summary>
    /// 获取从业人员
    /// </summary>
    /// <param name="dto"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task<IList<PractitionerItemOutDto>> GetPractitionerItemsAsync(PractitionerItemInDto dto, CancellationToken cancellationToken)
    {
        var items = await _practitionerRepository.Queryable()
            .Where(m => m.SystemAreaId == dto.AreaId)
            .OrderBy("RANDOM()")
            .Select<PractitionerItemOutDto>()
            .Take(dto.Count)
            .ToListAsync(cancellationToken);
        return items;
    }

    /// <summary>
    /// 获取从业人员详情
    /// </summary>
    /// <param name="id"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task<PractitionerDetailOutDto> GetPractitionerDetailAsync(string id, CancellationToken cancellationToken)
    {
        var item = await _practitionerRepository.GetAsync(id, cancellationToken)
            ?? throw UserFriendlyException.SameMessage("从业人员不存在");
        return item.Adapt<PractitionerDetailOutDto>();
    }
    #endregion

    #region 志愿者

    /// <summary>
    /// 添加志愿者
    /// </summary>
    /// <param name="dto"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task<string> AddVolunteerAsync(AddVolunteerInDto dto, CancellationToken cancellationToken)
    {
        var entity = dto.Adapt<Volunteer>();
        entity.Id = await _volunteerRepository.AddAsync(entity);
        return entity.Id;
    }

    /// <summary>
    /// 志愿者上报
    /// </summary>
    /// <param name="dto"></param>
    /// <param name="requestAborted"></param>
    /// <returns></returns>
    public async Task<AddVolunteerReportOutDto> AddVolunteerReportAsync(AddVolunteerReportInDto dto, CancellationToken requestAborted)
    {
        var volunteer = await _volunteerRepository.GetByPhoneAsync(_sessionContext.Phone)
                ?? throw UserFriendlyException.SameMessage("提交失败!您不是志愿者.");

        var entity = dto.Adapt<VolunteerReport>();
        entity.Volunteer = volunteer.Name;
        entity.VolunteerPhone = volunteer.PhoneNumber;

        entity.Id = await _volunteerReportRepository.AddAsync(entity, requestAborted);
        await _fileRepository.AddFileAsync(dto.Files, entity.Id, requestAborted);
        if (entity.Name.NotNullOrEmpty())
        {
            await _thirdAccountRepository.Updateable()
               .SetColumns(m => m.UserName, entity.Name)
               .Where(m => m.Id == _sessionContext.UserId)
               .ExecuteCommandAsync(requestAborted);
        }
        return entity.Adapt<AddVolunteerReportOutDto>();
    }
    #endregion

    #region 红包
    /// <summary>
    /// 生成用户红包审核数据
    /// </summary>
    /// <param name="orderId">工单Id</param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task<string> AddRedPardAsync(string orderId, CancellationToken cancellationToken)
    {
        var order = await _orderRepository.Queryable()
            .Where(m => m.Id == orderId)
            .Select(m => new { m.Id, m.Status, m.No, m.FromPhone })
            .FirstAsync(cancellationToken) ?? throw new UserFriendlyException($"{orderId} 工单不存在");
        var snapshot = await _orderSnapshotRepository.GetAsync(orderId) ?? throw new UserFriendlyException("工单不存在");
        if (order.Status != EOrderStatus.Filed) return $"{order.No} 工单状态非 {EOrderStatus.Filed} 不处理;";
        var redPack = await _redPackAuditRepository.GetByOrderIdAsync(orderId, cancellationToken);
        if (redPack != null) return $"{order.No} 工单已存在红包信息,不处理;";
        var industry = await _industryRepository.Queryable()
            .Where(m => m.Id == snapshot.IndustryId)
            .FirstAsync(cancellationToken);
        var entity = new RedPackAudit
        {
            OrderId = order.Id,
            Status = ERedPackAuditStatus.Pending,
            ShouldAmount = industry.CitizenReadPackAmount,
            PhoneNumber = order.FromPhone,
            IsSend = false,
        };
        await _redPackAuditRepository.AddAsync(entity, cancellationToken);
        var guiderAudit = entity.Adapt<RedPackGuiderAudit>();
        guiderAudit.ShouldAmount = industry.GuiderReadPackAmount;
        guiderAudit.PhoneNumber = snapshot.MemberMobile;
        guiderAudit.LevelTwoStatus = ERedPackAuditStatus.Pending;
        guiderAudit.ApprovedAmount = industry.GuiderReadPackAmount;
        await _redPackGuiderAuditRepository.AddAsync(guiderAudit, cancellationToken);
        return "ok";
    }
    #endregion

    #region 随手拍公告
    /// <summary>
    /// 添加随手拍公告
    /// </summary>
    /// <returns></returns>
    public async Task<string> AddBulletinAsync(AddSnapshotBulletinInDto dto)
    {
        dto.ValidateObject();
        var entity = dto.Adapt<SnapshotBulletin>();
        entity.BulletinState = EBulletinState.InReview;
        entity.Id = await _bulletinRepository.AddAsync(entity);
        return entity.Id;
    }

    /// <summary>
    /// 审核公告
    /// </summary>
    /// <param name="examineBulletinDto"></param>
    /// <returns></returns>
    public async Task AuditBulletinAsync(ExamineBulletinDto dto)
    {
        var bulletin = await _bulletinRepository.GetAsync(dto.Id)
            ?? throw UserFriendlyException.SameMessage("无效数据");
        if (bulletin.BulletinState != EBulletinState.InReview)
            throw UserFriendlyException.SameMessage("当前状态不能审核");

        bulletin.ExaminOpinion = dto.Reason;
        bulletin.ExaminTime = DateTime.Now;
        bulletin.ExaminManId = _sessionContext.RequiredUserId;
        if (dto.IsPass)
        {
            bulletin.BulletinState = EBulletinState.ReviewPass;
        }
        else
        {
            bulletin.BulletinState = EBulletinState.ReviewNoPass;
        }
        await _bulletinRepository.UpdateAsync(bulletin);
    }
    #endregion

    #region 积分
    public async Task<PointsRankOutDto> GetPointsRankAsync()
    {
        var outDto = new PointsRankOutDto();
        var record = await _pointsRecordRepository.Queryable()
            .Where(m => m.UserId == _sessionContext.UserId)
            .Select(m => new
            {
                Total = SqlFunc.AggregateSum(m.Points),
                Out = SqlFunc.AggregateSum(SqlFunc.IIF(m.Direction == EPointsDirection.Out, m.Points, 0))
            }).FirstAsync();
        outDto.ValidPoints = record.Total - record.Out;
        var startTime = new DateTime(DateTime.Now.Year, 1, 1, 0, 0, 0);
        var endTime = new DateTime(DateTime.Now.Year, 12, 31, 23, 59, 59);
        var query = _pointsRecordRepository.Queryable()
            .LeftJoin<Citizen>((points, citizen) => points.UserId == citizen.Id)
            .Where((points, citizen) => points.CreationTime >= startTime && points.CreationTime <= endTime)
            .GroupBy((points, citizen) => new { citizen.Id, points.UserId, citizen.IsSecurityMax, citizen.Name, citizen.PhoneNumber })
            .Select((points, citizen) => new PointsRankUserDto
            {
                IsSecurityMax = citizen.IsSecurityMax ?? false,
                Rank = SqlFunc.MappingColumn<int>($@" DENSE_RANK() OVER (ORDER BY SUM(CASE WHEN ""points"".""Direction"" = 0 THEN ""points"".""Points"" ELSE 0 END) DESC)"),
                Points = SqlFunc.AggregateSum(points.Points),
                UserName = citizen.Name!,
                PhoneNumber = citizen.PhoneNumber,
                CitizenId = citizen.Id,
            }).MergeTable()
            .Take(11);

#if DEBUG
        var sql = query.ToSqlString();
#endif
        var item = await query.ToListAsync();

        item.ForEach(m =>
        {
            if (m.IsSecurityMax)
                m.HeadUrl = _systemDicDataCacheManager.HeaderImages("aqws");
            else
                m.HeadUrl = _systemDicDataCacheManager.HeaderImages("default");
        });
        if (item.Any(m => m.CitizenId == _sessionContext.UserId) == false)
        {
            var my = (await _pointsRecordRepository.Queryable()
               .LeftJoin<Citizen>((points, citizen) => points.UserId == citizen.Id)
               .Where((points, citizen) => points.CreationTime >= startTime && points.CreationTime <= endTime)
               .GroupBy((points, citizen) => new { citizen.Id, points.UserId, citizen.IsSecurityMax, citizen.Name, citizen.PhoneNumber })
               .Select((points, citizen) => new PointsRankUserDto
               {
                   IsSecurityMax = citizen.IsSecurityMax ?? false,
                   Rank = SqlFunc.MappingColumn<int>($@" DENSE_RANK() OVER (ORDER BY SUM(CASE WHEN ""points"".""Direction"" = 0 THEN ""points"".""Points"" ELSE 0 END) DESC)"),
                   Points = SqlFunc.AggregateSum(points.Points),
                   UserName = citizen.Name!,
                   PhoneNumber = citizen.PhoneNumber,
                   CitizenId = citizen.Id,
               }).MergeTable()
               .ToListAsync()).FirstOrDefault(m => m.CitizenId == _sessionContext.UserId);
            if (my != null)
            {
                item.Insert(0, new PointsRankUserDto
                {
                    UserName = my.UserName,
                    CitizenId = my.CitizenId,
                    PhoneNumber = my.PhoneNumber,
                    Rank = my.Rank,
                    Points = my.Points,
                    HeadUrl = _systemDicDataCacheManager.HeaderImages("default"),
                    IsSecurityMax = false
                });
            }
            else
            {
                var citizen = await _citizenRepository.GetAsync(_sessionContext.UserId);
                var count = await _citizenRepository.CountAsync(m => m.Name != "");
                if (citizen != null)
                {
                    item.Insert(0, new PointsRankUserDto
                    {
                        UserName = citizen.Name,
                        CitizenId = citizen.Id,
                        PhoneNumber = citizen.PhoneNumber,
                        Rank = count,
                        Points = 0,
                        HeadUrl = _systemDicDataCacheManager.HeaderImages("default"),
                        IsSecurityMax = false
                    });
                }
            }
        }
        else
        {
            var my = item.First(m => m.CitizenId == _sessionContext.UserId);
            item.Remove(my);
            item.Insert(0, my);
        }
        outDto.Ranks = item;


        outDto.VideoBulletin = await _notificationReceiverRepository.Queryable()
            .LeftJoin<Notification>((receiver, notify) => notify.Id == receiver.NotificationId)
            .LeftJoin<SnapshotBulletin>((receiver, notify, bulletin) => bulletin.Id == notify.ExternalId)
            .Where((receiver, notify, bulletin) => receiver.ReceiverId == _sessionContext.UserId && bulletin.Shape == EBulletinShape.Video)
            .GroupBy((receiver, notify, bulletin) => new 
            {
                notify.Title,
                notify.ExternalId,
                bulletin.SnapshotBulletinTypeName,
                bulletin.VideoCoverImgUrl,
                notify.CreationTime,
                bulletin.VideoPath
            })
            .OrderByDescending((receiver, notify) => notify.CreationTime)
            .Select((receiver, notify, bulletin) => new VideoBulletinOutDto
            {
                Title = notify.Title,
                BulletinId = notify.ExternalId,
                UnReadCount = SqlFunc.AggregateSum(SqlFunc.IIF( receiver.IsRead == false, 1, 0)),
                SnapshotBulletinTypeName = bulletin.SnapshotBulletinTypeName,
                VideoCoverImgUrl = bulletin.VideoCoverImgUrl,
                VideoPath = bulletin.VideoPath,
            }).FirstAsync();

        outDto.Bulletins = new PointsBulletinOutDto
        {
            Items = await _notificationReceiverRepository.Queryable()
                .LeftJoin<Notification>((receiver, notify) => notify.Id == receiver.NotificationId)
                .LeftJoin<SnapshotBulletin>((receiver, notify, bulletin) => bulletin.Id == notify.ExternalId)
                .Where((receiver, notify, bulletin) => bulletin.Shape == EBulletinShape.Message && receiver.ReceiverId == _sessionContext.UserId)
                .GroupBy((receiver, notify, bulletin) => new { notify.Id, notify.ExternalId, notify.Title})
                .Select((receiver, notify, bulletin) => new PointsBulletinItemsOutDto
                {
                    BulletinId = notify.ExternalId,
                    NotificationId = notify.Id,
                    Title = notify.Title,
                }, true)
                .Take(2)
                .ToListAsync(),
            UnReadCount = await _notificationReceiverRepository.Queryable()
                .LeftJoin<Notification>((receiver, notify) => notify.Id == receiver.NotificationId)
                .LeftJoin<SnapshotBulletin>((receiver, notify, bulletin) => bulletin.Id == notify.ExternalId)
                .Where((receiver, notify, bulletin) => receiver.IsRead == false 
                && receiver.ReceiverId == _sessionContext.UserId 
                && bulletin.Shape == EBulletinShape.Message)
                .GroupBy((receiver, notify, bulletin) => new { receiver.ReceiverId, receiver.NotificationId})
                .CountAsync()
        };

        return outDto;
    }
    #endregion
}
