فهرست منبع

Merge branch 'feature/snapshot' into test

qinchaoyue 4 روز پیش
والد
کامیت
571e5978d2

+ 37 - 0
src/Hotline.Api/Controllers/Snapshot/SnapshotUserController.cs

@@ -0,0 +1,37 @@
+using Hotline.Application.Snapshot.Contracts;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Tools;
+using Microsoft.AspNetCore.Mvc;
+using System.ComponentModel;
+
+namespace Hotline.Api.Controllers.Snapshot;
+
+[Description("随手拍用户管理")]
+public class SnapshotUserController : BaseController
+{
+    private readonly ISnapshotUserApplication _snapshotUserApplication;
+
+    public SnapshotUserController(ISnapshotUserApplication snapshotUserApplication)
+    {
+        _snapshotUserApplication = snapshotUserApplication;
+    }
+
+    /// <summary>
+    /// 安全志愿者列表
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("citizen_relation")]
+    public async Task<PagedDto<CitizenRelationSafetyTypeOutDto>> GetCitizenRelationSafetyType([FromQuery] CitizenRelationSafetyTypeInDto dto)
+        => (await _snapshotUserApplication.GetCitizenRelationSafetyType(dto).ToPagedListAsync(dto)).ToPaged();
+
+    /// <summary>
+    /// 添加安全志愿者
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("citizen_relation")]
+    public async Task AddCitizenRelationSafetyTypeInDto([FromBody]AddCitizenRelationSafetyTypeInDto dto)
+        => await _snapshotUserApplication.AddCitizenRelationSafetyType(dto, HttpContext.RequestAborted);
+}

+ 24 - 0
src/Hotline.Application/Snapshot/Contracts/ISnapshotUserApplication.cs

@@ -0,0 +1,24 @@
+using Hotline.Share.Dtos.Snapshot;
+using SqlSugar;
+
+namespace Hotline.Application.Snapshot.Contracts;
+
+public interface ISnapshotUserApplication
+{
+    ISugarQueryable<CitizenRelationSafetyTypeOutDto> GetCitizenRelationSafetyType(CitizenRelationSafetyTypeInDto dto);
+
+    /// <summary>
+    /// 添加安全员类型和市民关系
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="token"></param>
+    /// <returns></returns>
+    Task AddCitizenRelationSafetyType(AddCitizenRelationSafetyTypeInDto dto, CancellationToken token);
+
+    /// <summary>
+    /// 删除关系
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task DeleteCitizenRelationSafetyAsync(DeleteCitizenRelationSafetyTypeInDto dto);
+}

+ 83 - 0
src/Hotline.Application/Snapshot/SnapshotUserApplication.cs

@@ -0,0 +1,83 @@
+using DocumentFormat.OpenXml.Vml.Office;
+using Hotline.Application.Snapshot.Contracts;
+using Hotline.Caching.Interfaces;
+using Hotline.Orders;
+using Hotline.Settings;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Tools;
+using Hotline.Snapshot;
+using Hotline.Snapshot.IRepository;
+using Hotline.ThirdAccountDomainServices;
+using Hotline.Tools;
+using Mapster;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Snapshot;
+
+public class SnapshotUserApplication : ISnapshotUserApplication, IScopeDependency
+{
+    private readonly ICitizenRepository _citizenRepository;
+    private readonly ISystemDicDataCacheManager _dicData;
+    private readonly IRepository<SafetyType> _safetyTypeRepository;
+
+    public SnapshotUserApplication(ICitizenRepository citizenRepository, ISystemDicDataCacheManager dicData, IRepository<SafetyType> safetyTypeRepository)
+    {
+        _citizenRepository = citizenRepository;
+        _dicData = dicData;
+        _safetyTypeRepository = safetyTypeRepository;
+    }
+
+    public async Task AddCitizenRelationSafetyType(AddCitizenRelationSafetyTypeInDto dto, CancellationToken token)
+    {
+        dto.ValidateObject();
+
+        var safeType = new SafetyType
+        {
+            Id = dto.SafetyTypeId,
+            Citizens = []
+        };
+        foreach (var item in dto.CitizenIds)
+        {
+            safeType.Citizens.Add(new Citizen { Id = item });
+        }
+
+        await _safetyTypeRepository.AddNav(safeType)
+            .Include(m => m.Citizens)
+            .ExecuteCommandAsync();
+    }
+
+    public ISugarQueryable<CitizenRelationSafetyTypeOutDto> GetCitizenRelationSafetyType(CitizenRelationSafetyTypeInDto dto)
+    {
+        var query = _citizenRepository.Queryable()
+            .LeftJoin<CitizenRelationSafetyType>((citizen, relation) => citizen.Id == relation.CitizenId)
+            .LeftJoin<SafetyType>((citizen, relation, safety) => relation.SafetyTypeId == safety.Id)
+            .WhereIF(dto.SafetyTypeId.NotNullOrEmpty(), (citizen, relation, safety) => safety.Id == dto.SafetyTypeId)
+            .Select((citizen, relation, safety) => new CitizenRelationSafetyTypeOutDto
+            {
+                CitizenId = citizen.Id,
+                CitizenName = citizen.Name,
+                PhoneNumber = citizen.PhoneNumber,
+                SafetyTypeName = safety.Name,
+                SafetyTypeId = safety.Id
+            });
+        return query;
+    }
+
+    public async Task DeleteCitizenRelationSafetyAsync(DeleteCitizenRelationSafetyTypeInDto dto)
+    {
+        foreach (var item in dto.Items)
+        {
+            await _citizenRepository.RemoveNav(m => m.Id == item.CitizenId)
+                .Include(m => m.SafetyTypes.Where(s => s.Id == item.SafetyTypeId).First())
+                .ExecuteCommandAsync();
+        }
+    }
+}

+ 19 - 0
src/Hotline.Repository.SqlSugar/Snapshot/SafetyTypeRepository.cs

@@ -0,0 +1,19 @@
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Snapshot;
+using Hotline.Snapshot.IRepository;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.Snapshot;
+
+public class SafetyTypeRepository : BaseRepository<SafetyType>, ISafetyTypeRepository, IScopeDependency
+{
+    public SafetyTypeRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder, IServiceProvider serviceProvider) : base(uow, dataPermissionFilterBuilder, serviceProvider)
+    {
+    }
+}

+ 76 - 1
src/Hotline.Share/Dtos/Snapshot/SnapshotUserInfoDto.cs

@@ -1,4 +1,6 @@
-using Hotline.Share.Tools;
+using Hotline.Share.Requests;
+using Hotline.Share.Tools;
+using System.ComponentModel.DataAnnotations;
 
 namespace Hotline.Share.Dtos.Snapshot;
 public class SnapshotUserInfoOutDto
@@ -49,3 +51,76 @@ public class SnapshotUserInfoOutDto
     public int AppraiseCount { get; set; }
 }
 
+public record CitizenRelationSafetyTypeInDto : PagedRequest
+{
+    /// <summary>
+    /// 志愿者类型
+    /// </summary>
+    public string? SafetyTypeId { get; set; }
+}
+
+public class CitizenRelationSafetyTypeOutDto
+{
+    /// <summary>
+    /// Id
+    /// </summary>
+    public string CitizenId { get; set; }
+
+    /// <summary>
+    /// 用户名
+    /// </summary>
+    public string? CitizenName { get; set; }
+
+    /// <summary>
+    /// 志愿者类型名称
+    /// </summary>
+    public string SafetyTypeName { get; set; }
+
+    /// <summary>
+    /// id
+    /// </summary>
+    public string SafetyTypeId { get; set; }
+
+    /// <summary>
+    /// 电话号码
+    /// </summary>
+    public string PhoneNumber { get; set; }
+}
+
+public class AddCitizenRelationSafetyTypeInDto
+{
+    /// <summary>
+    /// 志愿者类型
+    /// </summary>
+    [Required(ErrorMessage = "请选择志愿者类型")]
+    public string SafetyTypeId { get; set; }
+
+    /// <summary>
+    /// 市民Id
+    /// </summary>
+    [Required]
+    public IList<string> CitizenIds { get; set; }
+}
+
+
+public class DeleteCitizenRelationSafetyTypeInDto
+{
+    /// <summary>
+    /// 市民和志愿者类型集合
+    /// </summary>
+    public IList<CitizenIdRelationSafetyTypeId> Items { get; set; }
+}
+
+public class CitizenIdRelationSafetyTypeId
+{
+    /// <summary>
+    /// 市民Id
+    /// </summary>
+    [Required]
+    public string CitizenId { get; set; }
+
+    /// <summary>
+    /// 志愿者类型Id
+    /// </summary>
+    public string SafetyTypeId { get; set; }
+}

+ 14 - 0
src/Hotline.Share/Tools/TaskExtensions.cs

@@ -19,6 +19,20 @@ public static class TaskExtensions
         }
     }
 
+    public static async Task<string> Then<T>(this Task<T> task, Func<T, Task<string>> action, Func<T, Task<string>> nullCatch)
+    {
+        var result = await task;
+
+        if (result != null)
+        {
+            return await action(result);
+        }
+        else
+        {
+            return await nullCatch(result);
+        }
+    }
+
     /// <summary>
     /// 为 Task 类型的扩展方法,如果实体不为 null,则执行指定的操作。
     /// </summary>

+ 5 - 0
src/Hotline/Caching/Interfaces/ISysDicDataCacheManager.cs

@@ -106,5 +106,10 @@ namespace Hotline.Caching.Interfaces
         /// 随手拍重办原因
         /// </summary>
         IReadOnlyList<SystemDicData> InstaShotSpecialReason { get; }
+
+        /// <summary>
+        /// 随手拍安全员类型
+        /// </summary>
+        IReadOnlyList<SystemDicData> SafetyType { get; }
     }
 }

+ 5 - 0
src/Hotline/Caching/Services/SysDicDataCacheManager.cs

@@ -162,6 +162,11 @@ namespace Hotline.Caching.Services
 
         public IReadOnlyList<SystemDicData> InstaShotSpecialReason => GetOrAddDic(SysDicTypeConsts.InstaShotSpecialReason);
 
+        /// <summary>
+        /// 随手拍安全员类型
+        /// </summary>
+        public IReadOnlyList<SystemDicData> SafetyType => GetOrAddDic(SysDicTypeConsts.SafetyType);
+
         public void RemoveSysDicDataCache(string code)
         {
             _cacheSysDicData.Remove(code);

+ 3 - 0
src/Hotline/Orders/Citizen.cs

@@ -8,6 +8,7 @@ using Hotline.Orders;
 using Hotline.Quality;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Snapshot;
+using Hotline.Snapshot;
 using SqlSugar;
 using XF.Domain.Repository;
 
@@ -118,5 +119,7 @@ namespace Hotline.Orders
         public bool? IsSecurityMax { get; set; }
         #endregion
 
+		[Navigate(typeof(CitizenRelationSafetyType), nameof(CitizenRelationSafetyType.CitizenId), nameof(CitizenRelationSafetyType.SafetyTypeId))]
+        public List<SafetyType> SafetyTypes { get; set; }
     }
 }

+ 12 - 0
src/Hotline/SeedData/SystemDicDataSeedData.cs

@@ -203,6 +203,14 @@ public class SystemDicDataSeedData : ISeedData<SystemDicData>
                 new () { Id = "08dcd790-e54b-4da2-8a1a-bba70490b08c", DicDataValue = "随手拍整改时间过长", DicDataName = "随手拍整改时间过长", Sort = 8}
                 ];
         }
+        if (dicTypeCode == SysDicTypeConsts.SafetyType)
+        {
+            return [
+                new() { Id = "08dd7db8-5dc1-4f52-8d87-bc0772399a47", DicDataName = "安全志愿者", DicDataValue = "1", Sort = 1},
+                new() { Id = "08dd78a1-4823-4940-8ebd-8951d7a07e75", DicDataName = "宣传员", DicDataValue = "2", Sort = 2},
+                new() { Id = "08dd7bc9-44f5-4075-80dd-e4e5c3ee6af7", DicDataName = "安全卫士", DicDataValue = "3", Sort = 3},
+                ];
+        }
 
         throw new NotImplementedException();
     }
@@ -210,6 +218,10 @@ public class SystemDicDataSeedData : ISeedData<SystemDicData>
     public SystemDicType GetType(string dicTypeCode)
     {
         var dicType = new string[2];
+        if (dicTypeCode == SysDicTypeConsts.SafetyType)
+        {
+            dicType = ["08dd7c8d-974f-4c1b-84e9-d9aafcb53c1f", "安全志愿者类型"];
+        }
         if (dicTypeCode == SysDicTypeConsts.InstaShotSpecialReason)
         {
             dicType = ["81c202b7-5c50-45e5-bbde-fb1904957f85", "随手拍特提原因"];

+ 5 - 1
src/Hotline/Settings/SysDicTypeConsts.cs

@@ -341,5 +341,9 @@ public class SysDicTypeConsts
     /// 政治身份
     /// </summary>
     public static string PoliticalIdentity = "PoliticalIdentity";
-    
+
+    /// <summary>
+    /// 随手拍安全员类型
+    /// </summary>
+    public const string SafetyType = "SafetyType";
 }

+ 26 - 0
src/Hotline/Snapshot/CitizenRelationSafetyType.cs

@@ -0,0 +1,26 @@
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Entities;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 市民关联随手拍安全角色分类
+/// </summary>
+[Description("市民关联随手拍安全角色分类")]
+public class CitizenRelationSafetyType : ITable, IEntity
+{
+    /// <summary>
+    /// 市民Id
+    /// <inheritdoc cref="Hotline.Orders.Citizen"/>
+    /// </summary>
+    [SugarColumn(ColumnDescription = "市民Id", IsPrimaryKey = true)]
+    public string CitizenId { get; set; }
+
+    /// <summary>
+    /// 安全员类型Id
+    /// <inheritdoc cref="SafetyType"/>
+    /// </summary>
+    [SugarColumn(ColumnDescription = "安全员类型Id", IsPrimaryKey = true)]
+    public string SafetyTypeId { get; set; }
+}

+ 12 - 0
src/Hotline/Snapshot/IRepository/ISafetyTypeRepository.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Snapshot.IRepository;
+
+public interface ISafetyTypeRepository : IRepository<SafetyType>
+{
+}

+ 23 - 0
src/Hotline/Snapshot/SafetyType.cs

@@ -0,0 +1,23 @@
+using Hotline.Identity.Accounts;
+using Hotline.Orders;
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 安全卫士类型 
+/// </summary>
+[Description("安全卫士类型")]
+public class SafetyType : CreationSoftDeleteEntity
+{
+    /// <summary>
+    /// 名字
+    /// </summary>
+    [SugarColumn(ColumnDescription = "安全卫士类型名称")]
+    public string Name { get; set; }
+
+    [Navigate(typeof(CitizenRelationSafetyType), nameof(CitizenRelationSafetyType.SafetyTypeId), nameof(CitizenRelationSafetyType.CitizenId))]
+    public List<Citizen> Citizens { get; set; }
+}

+ 1 - 1
src/Hotline/Snapshot/Services/SnapshotPointsDomainService.cs

@@ -42,7 +42,7 @@ public class SnapshotPointsDomainService : ISnapshotPointsDomainService, IScopeD
             OrderId = orderId,
             Points = order.ReportPoints.Value,
             Source = source,
-            Direction = Share.Enums.CallCenter.EPointsDirection.In
+            Direction = Share.Enums.CallCenter.EPointsDirection.In,
         };
         if (source == EPointsSource.Report)
             points.Points = order.ReportPoints.Value;

+ 110 - 0
test/Hotline.Tests/Application/SnapshotUserApplicationTest.cs

@@ -0,0 +1,110 @@
+using Hotline.Api.Controllers;
+using Hotline.Application.Snapshot.Contracts;
+using Hotline.Caching.Interfaces;
+using Hotline.Identity.Accounts;
+using Hotline.Identity.Roles;
+using Hotline.Orders;
+using Hotline.Settings;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Tools;
+using Hotline.Snapshot;
+using Hotline.Snapshot.IRepository;
+using Hotline.ThirdAccountDomainServices;
+using Hotline.ThirdAccountDomainServices.Interfaces;
+using Hotline.Users;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Shouldly;
+using XF.Domain.Cache;
+using XF.Domain.Repository;
+
+namespace Hotline.Tests.Application;
+
+public class SnapshotUserApplicationTest : TestBase
+{
+    private readonly ISnapshotUserApplication _snapshotUserApplication;
+    private readonly ISystemDicDataCacheManager _dicData;
+    private readonly ICitizenRepository _citizenRepository;
+    private readonly ISafetyTypeRepository _safetyTypeRepository;
+    public SnapshotUserApplicationTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdIdentiyService, IThirdAccountRepository thirdAccountRepository, ITypedCache<SystemSetting> cacheSettingData, ThirdAccounSupplierFactory thirdAccountDomainFactory, IServiceProvider serviceProvider, ISnapshotUserApplication snapshotUserApplication, ISystemDicDataCacheManager dicData, ICitizenRepository citizenRepository, ISafetyTypeRepository safetyTypeRepository) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdIdentiyService, thirdAccountRepository, cacheSettingData, thirdAccountDomainFactory, serviceProvider)
+    {
+        _snapshotUserApplication = snapshotUserApplication;
+        _dicData = dicData;
+        _citizenRepository = citizenRepository;
+        _safetyTypeRepository = safetyTypeRepository;
+    }
+
+    [Fact]
+    public async Task SnapshotUserApplication_Test()
+    {
+        var newSafetyType = new SafetyType { Name = "安全卫士" };
+        var safetyType = await _safetyTypeRepository.Queryable().Where(m => m.Name == newSafetyType.Name).FirstAsync();
+        safetyType ??= new SafetyType
+            {
+                Id = await _safetyTypeRepository.AddAsync(newSafetyType)
+            };
+
+        var citizen = await _citizenRepository.Queryable()
+            .LeftJoin<CitizenRelationSafetyType>((citizen, relation) => relation.CitizenId == citizen.Id)
+            .Where((citizen, relation) => relation.CitizenId == null)
+            .FirstAsync();
+        var addDto = new AddCitizenRelationSafetyTypeInDto
+        {
+            CitizenIds = [citizen.Id],
+            SafetyTypeId = safetyType.Id,
+        };
+
+        await _snapshotUserApplication.AddCitizenRelationSafetyType(addDto, CancellationToken.None);
+
+        var inDto = new CitizenRelationSafetyTypeInDto
+        {
+            SafetyTypeId = safetyType.Id
+        };
+        var items = await _snapshotUserApplication.GetCitizenRelationSafetyType(inDto).ToListAsync();
+        var item = items.FirstOrDefault(m => m.CitizenId == citizen.Id && m.SafetyTypeId == safetyType.Id);
+        item.ShouldNotBeNull();
+        item.SafetyTypeName.ShouldBe(safetyType.Name);
+        item.SafetyTypeId.ShouldBe(safetyType.Id);
+        item.CitizenId.ShouldBe(citizen.Id);
+        item.CitizenName.ShouldBe(citizen.Name);
+
+        var newSafetyTypeXuanChuan = new SafetyType { Name = "宣传员" };
+        var safetyTypeXuanChuan = await _safetyTypeRepository.Queryable()
+            .Where(m => m.Name == newSafetyTypeXuanChuan.Name).FirstAsync();
+        safetyTypeXuanChuan ??= new SafetyType
+            {
+                Id = await _safetyTypeRepository.AddAsync(newSafetyTypeXuanChuan)
+            };
+        addDto = new AddCitizenRelationSafetyTypeInDto
+        {
+            CitizenIds = [citizen.Id],
+            SafetyTypeId = safetyTypeXuanChuan.Id,
+        };
+        await _snapshotUserApplication.AddCitizenRelationSafetyType(addDto, CancellationToken.None);
+        var deleteInDto = new DeleteCitizenRelationSafetyTypeInDto
+        {
+            Items =
+            [
+                new() {
+                    CitizenId = citizen.Id,
+                    SafetyTypeId = safetyTypeXuanChuan.Id
+                }
+            ]
+        };
+        await _snapshotUserApplication.DeleteCitizenRelationSafetyAsync(deleteInDto);
+
+        items = await _snapshotUserApplication.GetCitizenRelationSafetyType(inDto).ToListAsync();
+        item = items.FirstOrDefault(m => m.CitizenId == citizen.Id && m.SafetyTypeId == safetyType.Id);
+        item.ShouldBeNull();
+        inDto = new CitizenRelationSafetyTypeInDto
+        {
+            SafetyTypeId = safetyTypeXuanChuan.Id
+        };
+        items = await _snapshotUserApplication.GetCitizenRelationSafetyType(inDto).ToListAsync();
+        item = items.FirstOrDefault(m => m.CitizenId == citizen.Id && m.SafetyTypeId == safetyTypeXuanChuan.Id);
+        item.SafetyTypeName.ShouldBe(safetyTypeXuanChuan.Name);
+        item.SafetyTypeId.ShouldBe(safetyTypeXuanChuan.Id);
+        item.CitizenId.ShouldBe(citizen.Id);
+        item.CitizenName.ShouldBe(citizen.Name);
+    }
+}