Преглед на файлове

Merge branch 'feature/snapshot' into dev

qinchaoyue преди 5 дни
родител
ревизия
2690c21856
променени са 24 файла, в които са добавени 504 реда и са изтрити 19 реда
  1. 14 0
      src/Hotline.Api/Controllers/IdentityController.cs
  2. 37 0
      src/Hotline.Api/Controllers/Snapshot/SnapshotUserController.cs
  3. 8 0
      src/Hotline.Application/Identity/IIdentityAppService.cs
  4. 16 3
      src/Hotline.Application/Identity/IdentityAppService.cs
  5. 11 0
      src/Hotline.Application/Snapshot/Contracts/ISnapshotUserApplication.cs
  6. 26 2
      src/Hotline.Application/Snapshot/RedPackApplication.cs
  7. 70 0
      src/Hotline.Application/Snapshot/SnapshotUserApplication.cs
  8. 19 0
      src/Hotline.Repository.SqlSugar/Snapshot/SafetyTypeRepository.cs
  9. 53 1
      src/Hotline.Share/Dtos/Snapshot/SnapshotUserInfoDto.cs
  10. 37 0
      src/Hotline.Share/Dtos/Snapshot/ThirdTokenDto.cs
  11. 14 0
      src/Hotline.Share/Tools/TaskExtensions.cs
  12. 5 0
      src/Hotline/Caching/Interfaces/ISysDicDataCacheManager.cs
  13. 5 0
      src/Hotline/Caching/Services/SysDicDataCacheManager.cs
  14. 3 0
      src/Hotline/Orders/Citizen.cs
  15. 12 0
      src/Hotline/SeedData/SystemDicDataSeedData.cs
  16. 5 1
      src/Hotline/Settings/SysDicTypeConsts.cs
  17. 32 0
      src/Hotline/Snapshot/CitizenRelationSafetyType.cs
  18. 8 0
      src/Hotline/Snapshot/Contracts/ISnapshotPointsDomainService.cs
  19. 12 0
      src/Hotline/Snapshot/IRepository/ISafetyTypeRepository.cs
  20. 23 0
      src/Hotline/Snapshot/SafetyType.cs
  21. 15 10
      src/Hotline/Snapshot/Services/SnapshotPointsDomainService.cs
  22. 1 1
      src/TianQue.Sdk/Models/ApiReponse.cs
  23. 69 0
      test/Hotline.Tests/Application/SnapshotUserApplicationTest.cs
  24. 9 1
      test/Hotline.Tests/Infrastructure/TianQueTest.cs

+ 14 - 0
src/Hotline.Api/Controllers/IdentityController.cs

@@ -121,6 +121,20 @@ jxrWXHbT1FB6DqkdOnBbQqS1Azqz5HxLlSyEK3F60e3SgB5iZsDZ
     public async Task<Dictionary<string, object>> GetThirdTokenAsync([FromBody] ThirdTokenInDto dto)
         => await _identityAppService.GetThredTokenAsync(dto, HttpContext.RequestAborted);
 
+
+    /// <summary>
+    /// 第三方登录
+    /// </summary>
+    /// <param name=""></param>
+    /// <param name="_identityAppService"></param>
+    /// <param name=""></param>
+    /// <param name=""></param>
+    /// <returns></returns>
+    [AllowAnonymous]
+    [HttpPost("third/login")]
+    public async Task<Dictionary<string, object>> GetThirdLoginAsync([FromBody] ThirdOpenIdInDto dto)
+            => await _identityAppService.GetThredTokenAsync(dto, HttpContext.RequestAborted);
+
     /// <summary>
     /// 根据OpenId刷新令牌
     /// </summary>

+ 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);
+}

+ 8 - 0
src/Hotline.Application/Identity/IIdentityAppService.cs

@@ -22,6 +22,14 @@ namespace Hotline.Application.Identity
         /// <exception cref="UserFriendlyException"></exception>
         Task<Dictionary<string, object>> GetThredTokenAsync(ThirdTokenInDto dto, CancellationToken token);
 
+        /// <summary>
+        /// 第三方登录
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="token"></param>
+        /// <returns></returns>
+        Task<Dictionary<string, object>> GetThredTokenAsync(ThirdOpenIdInDto dto, CancellationToken token);
+
         /// <summary>
         /// 根据OpenId刷新令牌
         /// </summary>

+ 16 - 3
src/Hotline.Application/Identity/IdentityAppService.cs

@@ -336,14 +336,27 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
         thirdDto = await _thirdAccountDomainFactory.GetThirdParameterAsync(thirdDto, token);
         var thirdToken = await _thirdIdentiyFactory.GetTokenAsync(thirdDto, token);
         var phone = await _thirdIdentiyFactory.GetPhoneNumberAsync(thirdDto, token);
-        var thirdAccount = await _thirdAccountRepository.GetByOpenIdAsync(thirdToken.OpenId, token);
+
+        var inDto = new ThirdOpenIdInDto
+        { 
+            OpenId = thirdToken.OpenId,
+            UnionId = thirdToken.UnIonId,
+            AppType = dto.AppType,
+            ThirdType = dto.ThirdType,
+            PhoneNumber = phone.PhoneNumber
+        };
+        return await GetThredTokenAsync(inDto, token);
+    }
+
+    public async Task<Dictionary<string, object>> GetThredTokenAsync(ThirdOpenIdInDto dto, CancellationToken token)
+    {
+        var thirdAccount = await _thirdAccountRepository.GetByOpenIdAsync(dto.OpenId, token);
 
         // 新用户注册
         if (thirdAccount is null)
         {
             thirdAccount = dto.Adapt<ThirdAccount>();
-            thirdToken.Adapt(thirdAccount);
-            thirdAccount.PhoneNumber = phone.PhoneNumber;
+            thirdAccount.PhoneNumber = dto.PhoneNumber;
             thirdAccount.Id = await _thirdAccountRepository.AddAsync(thirdAccount);
             await _thirdAccountDomainFactory.RegisterAsync(thirdAccount, token);
             thirdAccount = await _thirdAccountRepository.GetAsync(thirdAccount.Id, token);

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

@@ -0,0 +1,11 @@
+using Hotline.Share.Dtos.Snapshot;
+using SqlSugar;
+
+namespace Hotline.Application.Snapshot.Contracts;
+
+public interface ISnapshotUserApplication
+{
+    ISugarQueryable<CitizenRelationSafetyTypeOutDto> GetCitizenRelationSafetyType(CitizenRelationSafetyTypeInDto dto);
+
+    Task AddCitizenRelationSafetyType(AddCitizenRelationSafetyTypeInDto dto, CancellationToken token);
+}

+ 26 - 2
src/Hotline.Application/Snapshot/RedPackApplication.cs

@@ -88,6 +88,26 @@ public class RedPackApplication : IRedPackApplication, IScopeDependency
             status = ERedPackAuditStatus.Agree;
         var redPackAudit = await _redPackAuditRepository.GetAsync(dto.RedPackAuditId, token) ?? throw UserFriendlyException.SameMessage("审核记录不存在");
         if (redPackAudit.Status != ERedPackAuditStatus.Pending) throw UserFriendlyException.SameMessage("已审核, 不可重复审核");
+
+        var industry = await _industryRepository.Queryable(includeDeleted: true)
+          .LeftJoin<OrderSnapshot>((i, o) => i.Id == o.IndustryId)
+          .Where((i, o) => o.Id == redPackAudit.OrderId)
+          .Select((i, o) => new {
+              i.Id,
+              i.CitizenReadPackAmount,
+              i.ArgeePoints,
+              i.ExtraDeductedPoints,
+              i.RefusePoints,
+              i.IsPoints,
+              o.IsSafetyDepartment,
+              i.Name
+          })
+          .FirstAsync(token);
+        redPackAudit.ApprovedAmount = redPackAudit.ShouldAmount;
+        if (industry.Name == "安全隐患" && industry.IsSafetyDepartment.HasValue && industry.IsSafetyDepartment == true)
+        {
+            redPackAudit.ApprovedAmount = 20;
+        }
         redPackAudit.SMSTemplateId = dto.SMSTemplateId;
         redPackAudit.Status = status;
         redPackAudit.AuditRemark = dto.Opinion;
@@ -97,7 +117,6 @@ public class RedPackApplication : IRedPackApplication, IScopeDependency
         redPackAudit.AuditTime = DateTime.Now;
         redPackAudit.AuditOrgId = _sessionContext.OrgId;
         redPackAudit.AuditOrgName = _sessionContext.OrgName;
-        redPackAudit.ApprovedAmount = redPackAudit.ShouldAmount;
         redPackAudit.Points = dto.Points;
         redPackAudit.PointsStatus = dto.PointsStatus;
         redPackAudit.PointsOpinion = dto.PointsOpinion;
@@ -247,9 +266,14 @@ public class RedPackApplication : IRedPackApplication, IScopeDependency
         var industry = await _industryRepository.Queryable(includeDeleted: true)
             .LeftJoin<OrderSnapshot>((i, o) => i.Id == o.IndustryId)
             .Where((i, o) => o.Id == id)
-            .Select((i, o) => new { i.Id, i.CitizenReadPackAmount, i.ArgeePoints, i.ExtraDeductedPoints, i.RefusePoints, i.IsPoints })
+            .Select((i, o) => new { i.Id, i.CitizenReadPackAmount, i.ArgeePoints, i.ExtraDeductedPoints, i.RefusePoints, i.IsPoints,
+            o.IsSafetyDepartment, i.Name})
             .FirstAsync();
         outDto.Amount = industry.CitizenReadPackAmount;
+        if (industry.Name == "安全隐患" && industry.IsSafetyDepartment.HasValue && industry.IsSafetyDepartment == true)
+        {
+            outDto.Amount = 20;
+        }
         outDto.ArgeePoints = industry.ArgeePoints;
         outDto.ExtraDeductedPoints = industry.ExtraDeductedPoints;
         outDto.RefusePoints = industry.RefusePoints;

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

@@ -0,0 +1,70 @@
+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
+        };
+        foreach (var item in dto.CitizenIds)
+        {
+            safeType.Citizens.Add(new Citizen { Id = item });
+        }
+
+        _safetyTypeRepository.AddNav(safeType);
+    }
+
+    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;
+    }
+}

+ 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)
+    {
+    }
+}

+ 53 - 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,53 @@ 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>
+    public IList<string> CitizenIds { get; set; }
+}
+

+ 37 - 0
src/Hotline.Share/Dtos/Snapshot/ThirdTokenDto.cs

@@ -6,6 +6,43 @@ using System.ComponentModel.DataAnnotations;
 
 namespace Hotline.Share.Dtos.Snapshot;
 
+public class ThirdOpenIdInDto
+{
+    /// <summary>
+    /// OpenId
+    /// </summary>
+    public string OpenId { get; set; }
+
+    /// <summary>
+    /// UnionId
+    /// </summary>
+    public string UnionId { get; set; }
+
+    /// <summary>
+    /// 第三方平台类型(不传默认微信)
+    /// 0: 微信
+    /// </summary>
+    public EThirdType ThirdType { get; set; } = EThirdType.WeChat;
+
+    /// <summary>
+    /// 登录app(不传默认随手拍)
+    /// 1: 随手拍
+    /// 2: 部门办件app
+    /// 3: 市民办件app
+    /// </summary>
+    public EAppType AppType { get; set; } = EAppType.Snapshot;
+
+    /// <summary>
+    /// 接口地址前缀
+    /// </summary>
+    public string? WebApiHost { get; set; }
+
+    /// <summary>
+    /// 电话号码
+    /// </summary>
+    public string? PhoneNumber { get; set; }
+}
+
 public class ThirdTokenInDto
 {
     /// <summary>

+ 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";
 }

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

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

+ 8 - 0
src/Hotline/Snapshot/Contracts/ISnapshotPointsDomainService.cs

@@ -12,5 +12,13 @@ namespace Hotline.Snapshot.Contracts;
 /// </summary>
 public interface ISnapshotPointsDomainService
 {
+    /// <summary>
+    /// 新增积分
+    /// </summary>
+    /// <param name="orderId"></param>
+    /// <param name="source"></param>
+    /// <param name="status"></param>
+    /// <param name="extraDeductedPoints"></param>
+    /// <returns></returns>
     Task AddPointsAsync(string orderId, EPointsSource source, ESnapshotSMSStatus? status, int? extraDeductedPoints);
 }

+ 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; }
+}

+ 15 - 10
src/Hotline/Snapshot/Services/SnapshotPointsDomainService.cs

@@ -36,19 +36,24 @@ public class SnapshotPointsDomainService : ISnapshotPointsDomainService, IScopeD
         if (order.ReportPoints.HasValue == false)
             throw new UserFriendlyException($"{order.Name} 行业未配置积分");
 
-        var point = 0;
-        if (source == EPointsSource.Report)
-            point = order.ReportPoints.Value;
-        if (source == EPointsSource.Audit && status == ESnapshotSMSStatus.Agree)
-            point = order.ArgeePoints ?? 0;
-        if (source == EPointsSource.Audit && status == ESnapshotSMSStatus.Refuse)
-            point = order.RefusePoints ?? 0 + extraDeductedPoints ?? 0;
-        await _pointsRecordRepository.AddAsync(new SnapshotPointsRecord
+        var points = new SnapshotPointsRecord
         {
             UserId = order.CreatorId,
             OrderId = orderId,
             Points = order.ReportPoints.Value,
-            Source = source
-        });
+            Source = source,
+            Direction = Share.Enums.CallCenter.EPointsDirection.In,
+        };
+        if (source == EPointsSource.Report)
+            points.Points = order.ReportPoints.Value;
+        if (source == EPointsSource.Audit && status == ESnapshotSMSStatus.Agree)
+            points.Points = order.ArgeePoints ?? 0;
+        if (source == EPointsSource.Audit && status == ESnapshotSMSStatus.Refuse)
+        {
+            points.Direction = Share.Enums.CallCenter.EPointsDirection.Out;
+            points.Points = order.RefusePoints ?? 0 + extraDeductedPoints ?? 0;
+            points.Points *= -1;
+        }
+        await _pointsRecordRepository.AddAsync(points);
     }
 }

+ 1 - 1
src/TianQue.Sdk/Models/ApiReponse.cs

@@ -43,5 +43,5 @@ public class AcceptInfoSuccessDto
     /// <summary>
     /// OrgId
     /// </summary>
-    public uint OrgId { get; set; }
+    public string OrgId { get; set; }
 }

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

@@ -0,0 +1,69 @@
+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();
+        if (newSafetyType == null)
+            safetyType.Id = await _safetyTypeRepository.AddAsync(newSafetyType);
+
+        var citizen = await _citizenRepository.Queryable()
+            .LeftJoin<CitizenRelationSafetyType>((citizen, relation) => relation.CitizenId == citizen.Id)
+            .Where((citizen, relation) => relation.Id == 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);
+    }
+}

+ 9 - 1
test/Hotline.Tests/Infrastructure/TianQueTest.cs

@@ -1,14 +1,20 @@
 using Hotline.File;
+using Hotline.Share.Tools;
+using Hotline.Snapshot.IRepository;
+using Shouldly;
 using TianQue.Sdk;
+using TianQue.Sdk.Models;
 
 namespace Hotline.Tests.Infrastructure;
 public class TianQueTest
 {
     private readonly IFileDomainService _fileDomainService;
+    private readonly IGuiderSystemService _tiqnQueService;
 
-    public TianQueTest(IFileDomainService fileDomainService)
+    public TianQueTest(IFileDomainService fileDomainService, IGuiderSystemService tiqnQueService)
     {
         _fileDomainService = fileDomainService;
+        _tiqnQueService = tiqnQueService;
     }
 
     [Fact]
@@ -34,6 +40,8 @@ public class TianQueTest
 
         //// Assert
         //Assert.Equal("ok", result);
+
+        //await _tiqnQueService.PostOrder(null, null, null);
     }
 
     [Fact]