Sfoglia il codice sorgente

完成一些测试

qinchaoyue 3 settimane fa
parent
commit
137cab60a3
23 ha cambiato i file con 549 aggiunte e 145 eliminazioni
  1. 1 1
      SnapshotWinFormsApp/App.config
  2. 0 57
      SnapshotWinFormsApp/Application/GriderApplication.cs
  3. 59 0
      SnapshotWinFormsApp/Application/GuiderApplication.cs
  4. 52 27
      SnapshotWinFormsApp/Application/Interfaces/ImportApplicationBase.cs
  5. 3 2
      SnapshotWinFormsApp/Application/InviteApplication.cs
  6. 8 17
      SnapshotWinFormsApp/Application/InviteLogApplication.cs
  7. 2 2
      SnapshotWinFormsApp/Application/OrderSnapshotApplication.cs
  8. 41 11
      SnapshotWinFormsApp/Application/SnapshotUserInfoApplication.cs
  9. 20 0
      SnapshotWinFormsApp/Application/VolunteerApplication.cs
  10. 29 0
      SnapshotWinFormsApp/Application/VolunteerReportApplication.cs
  11. 1 1
      SnapshotWinFormsApp/Entities/NewHotline/SystemDicData.cs
  12. 30 0
      SnapshotWinFormsApp/Entities/NewHotline/Volunteer.cs
  13. 165 0
      SnapshotWinFormsApp/Entities/NewHotline/VolunteerReport.cs
  14. 7 0
      SnapshotWinFormsApp/Entities/OldHotline/Flow03_PushContent.cs
  15. 1 1
      SnapshotWinFormsApp/Entities/OldHotline/OldInviteCodeRecord.cs
  16. 4 4
      SnapshotWinFormsApp/Entities/OldHotline/SSP_InviteEntity.cs
  17. 6 1
      SnapshotWinFormsApp/Entities/OldHotline/SSP_InviteLogEntity.cs
  18. 36 0
      SnapshotWinFormsApp/Entities/OldHotline/SSP_Volunteer.cs
  19. 23 0
      SnapshotWinFormsApp/Entities/OldHotline/SSP_VolunteerList.cs
  20. 11 11
      SnapshotWinFormsApp/MainForm.Designer.cs
  21. 1 3
      SnapshotWinFormsApp/Repository/TargetRepository.cs
  22. 39 5
      SnapshotWinFormsApp/Tools/MapsterConfig.cs
  23. 10 2
      SnapshotWinFormsApp/Tools/MyExtensions.cs

+ 1 - 1
SnapshotWinFormsApp/App.config

@@ -8,6 +8,6 @@
 	</appSettings>
 	<connectionStrings>
 		<add providerName="自贡" name ="SQLServerDB" connectionString="server=61.157.186.3,4368;database=ZG_CityHotline_Ver3;uid=ZGCityHotlineUser;pwd=fway09!@ZG_15;"  />
-		<add providerName="自贡" name ="PGSQLDB" connectionString="Server=110.188.24.182;Port=5432;UserId=dev;Password=fengwo11!!;Database=hotline_zg;" />
+		<add providerName="自贡" name ="PGSQLDB" connectionString="Server=110.188.24.182;Port=5432;UserId=dev;Password=fengwo11!!;Database=hotline_test;" />
 	</connectionStrings>
 </configuration>

+ 0 - 57
SnapshotWinFormsApp/Application/GriderApplication.cs

@@ -1,57 +0,0 @@
-using DataTransmission.Enum;
-using SnapshotWinFormsApp.Application.Dtos;
-using SnapshotWinFormsApp.Application.Interfaces;
-using SnapshotWinFormsApp.Entities.NewHotline;
-using SnapshotWinFormsApp.Entities.OldHotline;
-using SnapshotWinFormsApp.Repository;
-using SnapshotWinFormsApp.Repository.Enum;
-using SnapshotWinFormsApp.Repository.Interfaces;
-using SnapshotWinFormsApp.Tools;
-using SqlSugar;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SnapshotWinFormsApp.Application;
-
-[Description("导入网格员")]
-public class GriderApplication : ImportApplicationBase<Flow03_PushContent, Citizen, int>, IImportApplication
-{
-    private readonly ITargetRepository<Citizen> _userRepo;
-
-    public GriderApplication(CreateInstanceInDto inDto) : base(inDto)
-    {
-        _userRepo = new TargetRepository<Citizen>(inDto);
-    }
-
-    public override ISugarQueryable<Flow03_PushContent> GetSourceList()
-    {
-        return _sourceRepo.Queryable()
-            .Where(m => m.MemberMobile != null && m.MemberName != null)
-            .GroupBy(m => new { m.MemberName, m.MemberMobile });
-    }
-
-    public override async Task<bool> HasOldDataAsync(string tableName, Flow03_PushContent item, CancellationToken token)
-    {
-        var userId = await _userRepo.Queryable()
-            .Where(m => m.PhoneNumber == item.MemberMobile)
-            .Select(m => m.Id)
-            .FirstAsync(token);
-        return userId.IsNullOrEmpty();
-    }
-
-    public override async Task<Citizen> GetTargetAsync(Flow03_PushContent source, CancellationToken token)
-    {
-        var userInfo = new Citizen
-        {
-            PhoneNumber = source.MemberMobile.Replace("\t", ""),
-            Name = source.MemberName.Replace("\t", ""),
-            CitizenType = EReadPackUserType.Guider,
-            IdentityType = EIdentityType.Citizen
-        };
-        return await Task.FromResult(userInfo);
-    }
-}

+ 59 - 0
SnapshotWinFormsApp/Application/GuiderApplication.cs

@@ -0,0 +1,59 @@
+using DataTransmission.Enum;
+using SnapshotWinFormsApp.Application.Dtos;
+using SnapshotWinFormsApp.Application.Interfaces;
+using SnapshotWinFormsApp.Entities.NewHotline;
+using SnapshotWinFormsApp.Entities.OldHotline;
+using SnapshotWinFormsApp.Repository;
+using SnapshotWinFormsApp.Repository.Enum;
+using SnapshotWinFormsApp.Repository.Interfaces;
+using SnapshotWinFormsApp.Tools;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SnapshotWinFormsApp.Application;
+
+//[Description("导入网格员")]
+//public class GuiderApplication : ImportApplicationBase<Flow03_PushContent, Citizen, int, Flow03_PushContentDto>, IImportApplication
+//{
+//    private readonly ITargetRepository<Citizen> _userRepo;
+
+//    public GuiderApplication(CreateInstanceInDto inDto) : base(inDto)
+//    {
+//        _userRepo = new TargetRepository<Citizen>(inDto);
+//    }
+
+//    public override ISugarQueryable<Flow03_PushContentDto> GetSourceList()
+//    {
+//        return _sourceRepo.Queryable()
+//            .Where(m => m.MemberMobile != null && m.MemberName != null)
+//            .Select(m => new Flow03_PushContentDto(), true)
+//            .Distinct();
+//            //.GroupBy(m => new { m.MemberName, m.MemberMobile });
+//    }
+
+//    public override async Task<bool> HasOldDataAsync(Flow03_PushContentDto item, CancellationToken token)
+//    {
+//        var userId = await _userRepo.Queryable()
+//            .Where(m => m.PhoneNumber == item.MemberMobile)
+//            .Select(m => m.Id)
+//            .FirstAsync(token);
+//        return userId.IsNullOrEmpty();
+//    }
+
+//    public override async Task<Citizen> GetTargetAsync(Flow03_PushContentDto source, CancellationToken token)
+//    {
+//        var userInfo = new Citizen
+//        {
+//            PhoneNumber = source.MemberMobile.Replace("\t", ""),
+//            Name = source.MemberName.Replace("\t", ""),
+//            CitizenType = EReadPackUserType.Guider,
+//            IdentityType = EIdentityType.Citizen
+//        };
+//        return await Task.FromResult(userInfo);
+//    }
+//}

+ 52 - 27
SnapshotWinFormsApp/Application/Interfaces/ImportApplicationBase.cs

@@ -24,38 +24,73 @@ public class ImportApplicationBase<TSource, TEntity, TKey, TMix> : IImportApplic
     where TEntity : OldIdEntity, new()
     where TMix : OldBaseEntity<TKey>, new()
 {
-    public readonly ISourceRepository<TMix, TKey> _sourceRepo;
+    public readonly ISourceRepository<TSource, TKey> _sourceRepo;
     public readonly ITargetRepository<TEntity> _targetRepo;
     public readonly SqlSugarClient _sugarClient;
     public readonly CreateInstanceInDto _instance;
+    public readonly ISelectRepository<SystemDicData> _systemDicDataRepo;
+    public readonly IList<SystemDicData> JobType;
+    public List<TMix> Sources = new List<TMix>();
     public ImportApplicationBase(CreateInstanceInDto inDto)
     {
         _instance = inDto;
-        _sourceRepo = new SourceRepository<TMix, TKey>(inDto);
+        _sourceRepo = new SourceRepository<TSource, TKey>(inDto);
         _targetRepo = new TargetRepository<TEntity>(inDto);
+        _systemDicDataRepo = new SelectRepository<SystemDicData>(inDto);
+        JobType = _systemDicDataRepo.Queryable().Where(m => m.DicTypeCode == "JobType").ToList();
     }
 
     public async Task ImportAsync(Action<string> log, CancellationToken token)
     {
         log($"正在查询旧数据...");
-        var items = await GetSourceList(_instance).ToListAsync(token);
-        
-        log($"共查询到{items.Count}条数据");
-        var tableName = typeof(TSource).GetCustomAttribute<SugarTable>()?.TableName ?? throw new ArgumentNullException("老数据表名不能为空, 设置[SugarTable()]");
+        //var sql = GetSourceList().ToSqlString();
+        Sources = await GetSourceList().ToListAsync(token);
 
-        for (int i = 0;i < items.Count;i++)
+        log($"共查询到{Sources.Count}条数据");
+        //var tableName = typeof(TSource).GetCustomAttribute<SugarTable>()?.TableName ?? throw new ArgumentNullException("旧数据表名不能为空, 设置[SugarTable()]");
+
+        for (int i = 0;i < Sources.Count;i++)
         {
             await Task.Run(() => MainForm._pauseEvent.WaitHandle.WaitOne(), token);
-            var item = items[i];
-            var has = await HasOldDataAsync(tableName, item, token);
-            if (has) continue;
+            var item = Sources[i];
+            var has = true;
+            try
+            {
+                has = await HasOldDataAsync(item, token);
+
+            }
+            catch (Exception e)
+            {
+
+                if (e.Message.Contains("column \"OldId\" does not exist"))
+                {
+                    var tableName = new TEntity().GetTableName();
+                    log($"{tableName}表没有 OldId 字段");
+                    log($"ALTER TABLE \"public\".\"{tableName}\" ADD COLUMN \"OldId\" varchar(255);");
+                    return;
+                }
+            }
+            if (has)
+            {
+                log($"{i + 1}/{Sources.Count} 跳过已存在");
+                continue;
+            }
             var target = await GetTargetAsync(item, token);
             target.OldId = item.Id.ToString();
 
-            target.Id = _targetRepo.InsertBulk(target, i + 1 == items.Count);
-            log($"{i + 1}/{items.Count} 插入数据: {item.Id} {target.Id} {target.ToJson().Substring(0, 100)}");
-            await InsertAfterAsync(log, item, target, token);
+            target.Id = _targetRepo.InsertBulk(target, i + 1 == Sources.Count);
+            var msg = target.GetObjectValues();
+            if (msg.Length > 88)
+                msg = msg.Substring(0, 88);
+            log($"{i + 1}/{Sources.Count} 新增: {item.Id} {target.Id} {msg}");
+            await InsertAfterAsync(log, item, target, i + 1 == Sources.Count, token);
         }
+        await EndTaskAsync(log, token);
+    }
+
+    public virtual async Task EndTaskAsync(Action<string> log, CancellationToken token)
+    {
+        await Task.FromResult(0);
     }
 
     /// <summary>
@@ -66,7 +101,7 @@ public class ImportApplicationBase<TSource, TEntity, TKey, TMix> : IImportApplic
     /// <param name="token"></param>
     /// <returns></returns>
     /// <exception cref="NotImplementedException"></exception>
-    public virtual async Task InsertAfterAsync(Action<string> log, TMix item, TEntity target, CancellationToken token)
+    public virtual async Task InsertAfterAsync(Action<string> log, TMix item, TEntity target, bool isEnd, CancellationToken token)
     {
         await Task.FromResult(0);
     }
@@ -76,19 +111,9 @@ public class ImportApplicationBase<TSource, TEntity, TKey, TMix> : IImportApplic
     /// </summary>
     /// <param name="inDto"></param>
     /// <returns></returns>
-    public virtual ISugarQueryable<TMix> GetSourceList(CreateInstanceInDto inDto)
-    {
-        return _sourceRepo.Queryable();
-    }
-
-    /// <summary>
-    /// 获取原始数据集合
-    /// </summary>
-    /// <returns></returns>
-    /// <exception cref="NotImplementedException"></exception>
     public virtual ISugarQueryable<TMix> GetSourceList()
-    { 
-        throw new NotImplementedException();
+    {
+        return _sourceRepo.Queryable().Select(m => new TMix(), true);
     }
 
     /// <summary>
@@ -107,7 +132,7 @@ public class ImportApplicationBase<TSource, TEntity, TKey, TMix> : IImportApplic
     /// <param name="tableName"></param>
     /// <param name="item"></param>
     /// <returns></returns>
-    public virtual async Task<bool> HasOldDataAsync(string tableName, TMix item, CancellationToken token)
+    public virtual async Task<bool> HasOldDataAsync(TMix item, CancellationToken token)
     {
         return await _targetRepo.Queryable().AnyAsync(m => item.Id.ToString() == m.OldId, token);
     }

+ 3 - 2
SnapshotWinFormsApp/Application/InviteApplication.cs

@@ -14,18 +14,19 @@ using System.Configuration;
 namespace SnapshotWinFormsApp.Application;
 
 [Description("邀请码")]
-public class InviteApplication : ImportApplicationBase<SSP_InviteEntity, InviteCode, int, OldInviteCodeRecord>, IImportApplication
+public class InviteApplication : ImportApplicationBase<SSP_InviteEntity, InviteCode, string>, IImportApplication
 {
     public InviteApplication(CreateInstanceInDto inDto) : base(inDto)
     {
     }
 
-    public override async Task<InviteCode> GetTargetAsync(OldInviteCodeRecord source, CancellationToken token)
+    public override async Task<InviteCode> GetTargetAsync(SSP_InviteEntity source, CancellationToken token)
     {
         var inviteCode = source.Adapt<InviteCode>();
         var url = ConfigurationManager.AppSettings["ZiGongFile"] + inviteCode.QRCodeUrl;
         var fileContent = await new FileTools().GetNetworkFileAsync(url, token);
         inviteCode.QRCodeUrl = fileContent.Path;
+        inviteCode.CreationTime = DateTime.Now;
         return inviteCode;
     }
 }

+ 8 - 17
SnapshotWinFormsApp/Application/InviteLogApplication.cs

@@ -23,11 +23,11 @@ namespace SnapshotWinFormsApp.Application;
 public class InviteLogApplication : ImportApplicationBase<SSP_InviteLogEntity, InviteCodeRecord, string, OldInviteLogEntity>, IImportApplication
 {
     private readonly ITargetRepository<InviteCode> _newInviteCodeRepo;
-    private readonly ITargetRepository<InviteCodeRecord> _newInviteLogRepo;
     private readonly ITargetRepository<Citizen> _userInfoRepo;
     private readonly ITargetRepository<ThirdAccount> _thirdAccountRepo;
     private IList<InviteCode> invities;
     private IList<ThirdAccount> thirdAccounts;
+    public IList<InviteCodeRecord> InviteCodeRecords;
 
     public InviteLogApplication(CreateInstanceInDto inDto) : base(inDto)
     {
@@ -35,12 +35,13 @@ public class InviteLogApplication : ImportApplicationBase<SSP_InviteLogEntity, I
         _thirdAccountRepo = new TargetRepository<ThirdAccount>(inDto);
         invities = _newInviteCodeRepo.GetAll();
         thirdAccounts = _thirdAccountRepo.GetAll();
+        InviteCodeRecords = _targetRepo.GetAll();
     }
 
-    public override ISugarQueryable<OldInviteLogEntity> GetSourceList(CreateInstanceInDto inDto)
+    public override ISugarQueryable<OldInviteLogEntity> GetSourceList()
     {
-        return _sugarClient.Queryable<SSP_InviteLogEntity>()
-            .LeftJoin<SSP_InviteEntity>((log, invite) => log.SSPI_CodeID == invite.SIC_Byte)
+        return _sourceRepo.Queryable()
+            .LeftJoin<SSP_InviteEntity>((log, invite) => log.SSPI_CodeID == invite.Id)
             .LeftJoin<WeChatUserEntity>((log, invite, user) => user.WUR_Openid == log.SSPI_Openid)
             .Select((log, invite, user) => new OldInviteLogEntity
             {
@@ -50,12 +51,13 @@ public class InviteLogApplication : ImportApplicationBase<SSP_InviteLogEntity, I
                 InviteCode = log.SSPI_Code,
                 WXOpenId = log.SSPI_Openid,
                 PhoneNumber = user.WUR_PhoneNum,
+                CreationTime = log.SSPI_AddTime
             });
     }
 
-    public override async Task<bool> HasOldDataAsync(string tableName, OldInviteLogEntity item, CancellationToken token)
+    public override async Task<bool> HasOldDataAsync(OldInviteLogEntity item, CancellationToken token)
     {
-        return await _newInviteLogRepo.Queryable().AnyAsync(m => m.Id == item.Id, token);
+        return InviteCodeRecords.Any(m => m.OldId == item.Id);
     }
 
     public override async Task<InviteCodeRecord> GetTargetAsync(OldInviteLogEntity source, CancellationToken token)
@@ -64,15 +66,4 @@ public class InviteLogApplication : ImportApplicationBase<SSP_InviteLogEntity, I
         target.OrgId = invities.FirstOrDefault(m => m.OrgName == source.OrgName)?.Id ?? string.Empty;
         return await Task.FromResult(target);
     }
-
-    public override async Task InsertAfterAsync(Action<string> log,  OldInviteLogEntity item, InviteCodeRecord target, CancellationToken token)
-    {
-        var userId = thirdAccounts.Where(m => m.OpenId == item.WXOpenId).Select(m => m.ExternalId).FirstOrDefault();
-        if (userId.IsNullOrEmpty()) return;
-
-        await _userInfoRepo.Updateable()
-                 .SetColumns(m => m.InvitationCode, item.InviteCode)
-                 .Where(m => m.Id == userId)
-                 .ExecuteCommandAsync(token);
-    }
 }

+ 2 - 2
SnapshotWinFormsApp/Application/OrderSnapshotApplication.cs

@@ -35,7 +35,7 @@ public class OrderSnapshotApplication : IImportApplication
     private readonly List<CommunityInfo> communityInfos;
     private readonly ITargetRepository<Entities.NewHotline.User> _userRepo;
     private readonly List<Entities.NewHotline.User> _users;
-    private readonly ITargetRepository<SystemDicData> _systemDicDataRepo;
+    private readonly ISelectRepository<SystemDicData> _systemDicDataRepo;
     private readonly List<SystemDicData> snapshotOrderLabels;
     public OrderSnapshotApplication(CreateInstanceInDto inDto)
     {
@@ -48,7 +48,7 @@ public class OrderSnapshotApplication : IImportApplication
         communityInfos = _communityInfoRepo.Queryable().ToList();
         _userRepo = new TargetRepository<Entities.NewHotline.User>(inDto);
         _users = _userRepo.Queryable().ToList();
-        _systemDicDataRepo = new TargetRepository<SystemDicData>(inDto);
+        _systemDicDataRepo = new SelectRepository<SystemDicData>(inDto);
         snapshotOrderLabels = _systemDicDataRepo.Queryable().Where(m => m.DicTypeCode == "SnapshotOrderLabel").ToList();
 
         config.Name = "自贡市";

+ 41 - 11
SnapshotWinFormsApp/Application/SnapshotUserInfoApplication.cs

@@ -1,5 +1,4 @@
-using Abp.Extensions;
-using DataTransmission.Enum;
+using DataTransmission.Enum;
 using Mapster;
 using SnapshotWinFormsApp.Application.Dtos;
 using SnapshotWinFormsApp.Application.Interfaces;
@@ -8,6 +7,7 @@ using SnapshotWinFormsApp.Entities.OldHotline;
 using SnapshotWinFormsApp.Repository;
 using SnapshotWinFormsApp.Repository.Enum;
 using SnapshotWinFormsApp.Repository.Interfaces;
+using SnapshotWinFormsApp.Tools;
 using SqlSugar;
 using System;
 using System.Collections.Generic;
@@ -20,16 +20,45 @@ using System.Xml.Linq;
 
 namespace SnapshotWinFormsApp.Application;
 
-[Description("导入用户信息")]
+[Description("微信用户和网格员")]
 public class SnapshotUserInfoApplication : ImportApplicationBase<WeChatUserEntity, Citizen, int>, IImportApplication
 {
     private readonly ITargetRepository<ThirdAccount> _thirdAccountRepo;
     private readonly ITargetRepository<Citizen> _userRepo;
+    private readonly IList<Citizen> Users;
+    private readonly IList<Flow03_PushContentDto> Guiders;
+    private readonly IList<SSP_InviteLogEntity> InviteLogs;
+    private readonly ITargetRepository<InviteCodeRecord> _inviteCodeRecordRepository;
 
     public SnapshotUserInfoApplication(CreateInstanceInDto inDto) : base(inDto)
     {
+        _inviteCodeRecordRepository = new TargetRepository<InviteCodeRecord>(inDto);
         _thirdAccountRepo = new TargetRepository<ThirdAccount>(inDto);
         _userRepo = new TargetRepository<Citizen>(inDto);
+        Users = _userRepo.GetAll();
+        Guiders = new SourceRepository<Flow03_PushContent, int>(inDto).Queryable()
+            .Where(m => m.MemberMobile != null && m.MemberName != null)
+            .Select(m => new Flow03_PushContentDto(), true)
+            .Distinct().ToList();
+        InviteLogs = new SourceRepository<SSP_InviteLogEntity, string>(inDto).Queryable()
+            .ToList();
+    }
+
+    public override async Task<Citizen> GetTargetAsync(WeChatUserEntity source, CancellationToken token)
+    {
+        var target = source.Adapt<Citizen>();
+        var guider = Guiders.Where(m => m.MemberMobile == target.PhoneNumber).FirstOrDefault();
+        target.CitizenType = EReadPackUserType.Citizen;
+        if (guider != null)
+        {
+            target.CitizenType = EReadPackUserType.Guider;
+            target.LastModificationName = guider.MemberName;
+        }
+
+        var code = InviteLogs.Where(m => m.SSPI_Openid == source.WUR_Openid).FirstOrDefault();
+        if (code != null)
+            target.InvitationCode = code.SSPI_Code;
+        return target;
     }
 
     public override ISugarQueryable<WeChatUserEntity> GetSourceList()
@@ -38,16 +67,15 @@ public class SnapshotUserInfoApplication : ImportApplicationBase<WeChatUserEntit
             .Where(m => m.WUR_UserType == "ssp");
     }
 
-    public override async Task<bool> HasOldDataAsync(string tableName, WeChatUserEntity item, CancellationToken token)
+    public override async Task<bool> HasOldDataAsync(WeChatUserEntity item, CancellationToken token)
     {
-        var userId = await _userRepo.Queryable()
+        var userId = Users
             .Where(m => m.PhoneNumber == item.WUR_PhoneNum)
-            .Select(m => m.Id)
-            .FirstAsync(token);
-        return userId.IsNullOrEmpty();
+            .Select(m => m.Id).FirstOrDefault();
+        return userId.NotNullOrEmpty();
     }
 
-    public override async Task InsertAfterAsync(Action<string> log, WeChatUserEntity item, Citizen target, CancellationToken token)
+    public override async Task InsertAfterAsync(Action<string> log, WeChatUserEntity item, Citizen target, bool isEnd, CancellationToken token)
     {
         var thirdId = await _thirdAccountRepo.Queryable()
             .Where(m => m.OpenId == item.WUR_Openid && m.UnIonId == item.WUR_unionid)
@@ -57,8 +85,10 @@ public class SnapshotUserInfoApplication : ImportApplicationBase<WeChatUserEntit
         {
             var thirdAccount = item.Adapt<ThirdAccount>();
             thirdAccount.ExternalId = target.Id;
-            thirdAccount.Id = await _thirdAccountRepo.InsertAsync(thirdAccount, token);
-            log($"插入第三方账号信息: {thirdAccount.Id}, {thirdAccount.OpenId}, {thirdAccount.UserName}, {thirdAccount.PhoneNumber}, {thirdAccount.ExternalId}");
+            if (thirdAccount.OpenId.IsNullOrEmpty())
+            { log("openid为空"); return; }
+            thirdAccount.Id = _thirdAccountRepo.InsertBulk(thirdAccount, isEnd);
+            //log($"插入第三方账号信息: {thirdAccount.Id}, {thirdAccount.OpenId}, {thirdAccount.UserName}, {thirdAccount.PhoneNumber}, {thirdAccount.ExternalId}");
         }
     }
 }

+ 20 - 0
SnapshotWinFormsApp/Application/VolunteerApplication.cs

@@ -0,0 +1,20 @@
+using Hotline.Snapshot;
+using SnapshotWinFormsApp.Application.Dtos;
+using SnapshotWinFormsApp.Application.Interfaces;
+using SnapshotWinFormsApp.Entities.OldHotline;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SnapshotWinFormsApp.Application;
+
+[Description("志愿者")]
+public class VolunteerApplication : ImportApplicationBase<SSP_VolunteerList, Volunteer, int>, IImportApplication
+{
+    public VolunteerApplication(CreateInstanceInDto inDto) : base(inDto)
+    {
+    }
+}

+ 29 - 0
SnapshotWinFormsApp/Application/VolunteerReportApplication.cs

@@ -0,0 +1,29 @@
+using Hotline.Snapshot;
+using Mapster;
+using SnapshotWinFormsApp.Application.Dtos;
+using SnapshotWinFormsApp.Application.Interfaces;
+using SnapshotWinFormsApp.Entities.OldHotline;
+using SnapshotWinFormsApp.Repository.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SnapshotWinFormsApp.Application;
+
+[Description("志愿者上报")]
+public class VolunteerReportApplication : ImportApplicationBase<SSP_Volunteer, VolunteerReport, int>, IImportApplication
+{
+    public VolunteerReportApplication(CreateInstanceInDto inDto) : base(inDto)
+    {
+    }
+
+    public override async Task<VolunteerReport> GetTargetAsync(SSP_Volunteer source, CancellationToken token)
+    {
+        var target = source.Adapt<VolunteerReport>();
+        target.JobType = JobType.FirstOrDefault(m => m.DicDataName == target.JobType)?.Id;
+        return target;
+    }
+}

+ 1 - 1
SnapshotWinFormsApp/Entities/NewHotline/SystemDicData.cs

@@ -5,7 +5,7 @@ namespace SnapshotWinFormsApp.Entities.NewHotline
 {
     [SugarTable("system_dic_data")]
     [Description("字典表")]
-    public class SystemDicData : FullStateEntity
+    public class SystemDicData : DataTransmission.Entity.FullStateEntity
 	{
         /// <summary>
         /// 字典类型Id

+ 30 - 0
SnapshotWinFormsApp/Entities/NewHotline/Volunteer.cs

@@ -0,0 +1,30 @@
+using SnapshotWinFormsApp.Entities.NewHotline;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 志愿者
+/// </summary>
+[SugarTable("volunteer")]
+[Description("志愿者")]
+public class Volunteer : FullStateEntity
+{
+    /// <summary>
+    /// 姓名
+    /// </summary>
+    [SugarColumn(ColumnDescription ="姓名")]
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 电话
+    /// </summary>
+    [SugarColumn(ColumnDescription = "电话")]
+    public string PhoneNumber { get; set; }
+}

+ 165 - 0
SnapshotWinFormsApp/Entities/NewHotline/VolunteerReport.cs

@@ -0,0 +1,165 @@
+using SnapshotWinFormsApp.Entities.NewHotline;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 志愿者上报
+/// </summary>
+[SugarTable("volunteer_report")]
+[Description("志愿者上报")]
+public class VolunteerReport : FullStateEntity
+{
+    /// <summary>
+    /// 作业类型
+    /// </summary>
+    [SugarColumn(ColumnDescription = "作业类型")]
+    public string JobType { get; set; }
+
+    /// <summary>
+    /// 上报人联系方式
+    /// </summary>
+    [SugarColumn(ColumnDescription = "上报人联系方式")]
+    public string PhoneNumber { get; set; }
+
+    /// <summary>
+    /// 上报人姓名
+    /// </summary>
+    [SugarColumn(ColumnDescription = "上报人姓名")]
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 申报人手机号码
+    /// </summary>
+    public string? DeclarePhoneNumber { get; set; }
+
+    /// <summary>
+    /// 志愿者姓名
+    /// </summary>
+    [SugarColumn(ColumnDescription = "志愿者姓名")]
+    public string Volunteer { get; set; }
+
+    /// <summary>
+    /// 志愿者电话
+    /// </summary>
+    [SugarColumn(ColumnDescription = "志愿者电话")]
+    public string VolunteerPhone { get; set; }
+
+    /// <summary>
+    /// 是否已经申报
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否已经申报")]
+    public bool IsDeclare { get; set; }
+
+    /// <summary>
+    /// 生产经营单位内部是否按规定办理审批手续
+    /// </summary>
+    [SugarColumn(ColumnDescription = "生产经营单位内部是否按规定办理审批手续")]
+    public bool IsApprovalProcess { get; set; }
+
+    /// <summary>
+    /// 电气焊作业人员是否取得职业资格证书
+    /// </summary>
+    [SugarColumn(ColumnDescription = "电气焊作业人员是否取得职业资格证书")]
+    public bool IsProfessionalCertificate { get; set; }
+
+    /// <summary>
+    /// 是否落实作业现场监护人员
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否落实作业现场监护人员")]
+    public bool IsSiteMonitoring { get; set; }
+
+    /// <summary>
+    /// 是否在人员密集场所营业期间动火作业
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否在人员密集场所营业期间动火作业")]
+    public bool IsFireWork { get; set; }
+
+    /// <summary>
+    /// 是否清除作业现场及周围易燃物品或落实有效安全防范措施
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否清除作业现场及周围易燃物品或落实有效安全防范措施")]
+    public bool IsClearSafety { get; set; }
+
+    /// <summary>
+    /// 作业现场是否配备能满足现场灭火应急需求消防器材
+    /// </summary>
+    [SugarColumn(ColumnDescription = "作业现场是否配备能满足现场灭火应急需求消防器材")]
+    public bool HasFireEquipment { get; set; }
+
+    /// <summary>
+    /// 作业现场使用的工器具是否进行安全检查
+    /// </summary>
+    [SugarColumn(ColumnDescription = "作业现场使用的工器具是否进行安全检查")]
+    public bool IsToolSafety { get; set; }
+
+    #region 地址信息
+    /// <summary>
+    /// 经度
+    /// </summary>
+    [SugarColumn(ColumnDescription = "经度")]
+    public double? Longitude { get; set; }
+
+    /// <summary>
+    /// 维度
+    /// </summary>
+    [SugarColumn(ColumnDescription = "维度")]
+    public double? Latitude { get; set; }
+
+    /// <summary>
+    /// 行政区划编码;
+    /// Area对象的Id;
+    /// </summary>
+    [SugarColumn(ColumnDescription = "行政区编码")]
+    public string? AreaCode { get; set; }
+
+    /// <summary>
+    /// 省
+    /// </summary>
+    [SugarColumn(ColumnDescription = "省")]
+    public string? Province { get; set; }
+
+    /// <summary>
+    /// 市
+    /// </summary>
+    [SugarColumn(ColumnDescription = "市")]
+    public string? City { get; set; }
+
+    /// <summary>
+    /// 区/县
+    /// </summary>
+    [SugarColumn(ColumnDescription = "区县")]
+    public string? County { get; set; }
+
+    /// <summary>
+    /// 乡镇(4级行政区划)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "4级行政区划")]
+    public string? Town { get; set; }
+
+    /// <summary>
+    /// 详细街道
+    /// </summary>
+    [SugarColumn(ColumnDescription = "详细街道")]
+    public string? Street { get; set; }
+
+    /// <summary>
+    /// 行政区划地址
+    /// </summary>
+    [SugarColumn(ColumnDescription = "行政区划地址")]
+    public string? Address { get; set; }
+
+    /// <summary>
+    /// 完整地址
+    /// </summary>
+    [SugarColumn(ColumnDescription = "完整地址")]
+    public string? FullAddress { get; set; }
+
+    #endregion
+}

+ 7 - 0
SnapshotWinFormsApp/Entities/OldHotline/Flow03_PushContent.cs

@@ -45,3 +45,10 @@ public class Flow03_PushContent : OldBaseEntity<int>
     public DateTime? FPC_TagDate { get; set; }
     public int? FPC_PDUserID { get; set; }
 }
+
+public class Flow03_PushContentDto 
+{
+    public string? MemberName { get; set; }
+    public string? MemberMobile { get; set; }
+
+}

+ 1 - 1
SnapshotWinFormsApp/Entities/OldHotline/OldInviteCodeRecord.cs

@@ -6,6 +6,6 @@ using System.Threading.Tasks;
 
 namespace SnapshotWinFormsApp.Entities.OldHotline;
 
-public class OldInviteCodeRecord : OldBaseEntity<int>
+public class OldInviteCodeRecord : OldBaseEntity<string>
 {
 }

+ 4 - 4
SnapshotWinFormsApp/Entities/OldHotline/SSP_InviteEntity.cs

@@ -8,10 +8,10 @@ using System.Threading.Tasks;
 namespace SnapshotWinFormsApp.Entities.OldHotline;
 
 [SugarTable("ZG_CityHotline_Ver3.dbo.SSP_InviteCode")]
-public class SSP_InviteEntity : OldBaseEntity<int>
+public class SSP_InviteEntity : OldBaseEntity<string>
 {
-    [SugarColumn(ColumnName = "SIC_ID")]
-    public override int Id { get; set; }
+    [SugarColumn(ColumnName = "SIC_Byte")]
+    public override string Id { get; set; }
     //public int SIC_ID { get; set; }
     public int SIC_PID { get; set; }
     public string SIC_BMName { get; set; }
@@ -21,5 +21,5 @@ public class SSP_InviteEntity : OldBaseEntity<int>
     public string SIC_imgUrl { get; set; }
     public int SIC_OrderID { get; set; }
     public string SIC_Codeold { get; set; }
-    public string SIC_Byte { get; internal set; }
+    //public string SIC_Byte { get; internal set; }
 }

+ 6 - 1
SnapshotWinFormsApp/Entities/OldHotline/SSP_InviteLogEntity.cs

@@ -18,7 +18,7 @@ public class SSP_InviteLogEntity: OldBaseEntity<string>
     public string SSPI_Code { get; set; }
     public string SSPI_Remarks { get; set; }
     public string SSPI_CodeID { get; set; }
-    public int SSPI_Type { get; set; }
+    public string SSPI_Type { get; set; }
 }
 
 public class OldInviteLogEntity : OldBaseEntity<string>
@@ -58,4 +58,9 @@ public class OldInviteLogEntity : OldBaseEntity<string>
     /// </summary>
     [SugarColumn(ColumnDescription = "姓名")]
     public string? Name { get; set; }
+
+    /// <summary>
+    /// 时间
+    /// </summary>
+    public DateTime CreationTime { get; set; }
 }

+ 36 - 0
SnapshotWinFormsApp/Entities/OldHotline/SSP_Volunteer.cs

@@ -0,0 +1,36 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SnapshotWinFormsApp.Entities.OldHotline;
+
+public class SSP_Volunteer : OldBaseEntity<int>
+{
+    [SugarColumn(ColumnName = "SV_ID")]
+    public override int Id { get => base.Id; set => base.Id = value; }
+    //public int SV_ID { get; set; }
+    public string SV_WorkType { get; set; }
+    public string SV_Name { get; set; }
+    public string SV_Tel { get; set; }
+    public string SV_ISDeclare { get; set; }
+    public string SV_Address { get; set; }
+    public string SV_Address2 { get; set; }
+    public string SV_latLon { get; set; }
+    public string SV_Remarks { get; set; }
+    public string SV_FileKey { get; set; }
+    public string SV_DeclareUserName { get; set; }
+    public string SV_DeclareUserTel { get; set; }
+    public string SV_AddDate { get; set; }
+    public string SV_WXOpenid { get; set; }
+    public string SV_InspectionItems1 { get; set; }
+    public string SV_InspectionItems2 { get; set; }
+    public string SV_InspectionItems3 { get; set; }
+    public string SV_InspectionItems4 { get; set; }
+    public string SV_InspectionItems5 { get; set; }
+    public string SV_InspectionItems6 { get; set; }
+    public string SV_InspectionItems7 { get; set; }
+    public string SV_InspectionItems8 { get; set; }
+}

+ 23 - 0
SnapshotWinFormsApp/Entities/OldHotline/SSP_VolunteerList.cs

@@ -0,0 +1,23 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SnapshotWinFormsApp.Entities.OldHotline;
+
+[SugarTable("ZG_CityHotline_Ver3.dbo.SSP_VolunteerList")]
+public class SSP_VolunteerList : OldBaseEntity<int>
+{
+    [SugarColumn(ColumnName = "SVL_ID")]
+    public override int Id { get => base.Id; set => base.Id = value; }
+    //public int SVL_ID { get; set; }
+    public string SVL_Name { get; set; }
+    public string SVL_Tel { get; set; }
+    public string SVL_Code { get; set; }
+    public string SVL_Age { get; set; }
+    public string SVL_Education { get; set; }
+    public string SVL_Address { get; set; }
+    public string SVL_Industry { get; set; }
+}

+ 11 - 11
SnapshotWinFormsApp/MainForm.Designer.cs

@@ -60,9 +60,9 @@ partial class MainForm
         // CancelBtn
         // 
         CancelBtn.Dock = DockStyle.Fill;
-        CancelBtn.Location = new Point(128, 362);
+        CancelBtn.Location = new Point(128, 63);
         CancelBtn.Name = "CancelBtn";
-        CancelBtn.Size = new Size(119, 161);
+        CancelBtn.Size = new Size(119, 460);
         CancelBtn.TabIndex = 0;
         CancelBtn.Text = "停止任务";
         CancelBtn.UseVisualStyleBackColor = true;
@@ -91,8 +91,8 @@ partial class MainForm
         // tableLayoutPanel1
         // 
         tableLayoutPanel1.ColumnCount = 2;
-        tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
-        tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
+        tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
+        tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
         tableLayoutPanel1.Controls.Add(cityNameCbox, 0, 0);
         tableLayoutPanel1.Controls.Add(OkBtn, 0, 2);
         tableLayoutPanel1.Controls.Add(CancelBtn, 1, 2);
@@ -102,9 +102,9 @@ partial class MainForm
         tableLayoutPanel1.Location = new Point(0, 0);
         tableLayoutPanel1.Name = "tableLayoutPanel1";
         tableLayoutPanel1.RowCount = 3;
-        tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 42.27848F));
-        tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 57.72152F));
-        tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 166F));
+        tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 30F));
+        tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 30F));
+        tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 30F));
         tableLayoutPanel1.Size = new Size(250, 526);
         tableLayoutPanel1.TabIndex = 1;
         // 
@@ -122,9 +122,9 @@ partial class MainForm
         // OkBtn
         // 
         OkBtn.Dock = DockStyle.Fill;
-        OkBtn.Location = new Point(3, 362);
+        OkBtn.Location = new Point(3, 63);
         OkBtn.Name = "OkBtn";
-        OkBtn.Size = new Size(119, 161);
+        OkBtn.Size = new Size(119, 460);
         OkBtn.TabIndex = 0;
         OkBtn.Text = "开始导入";
         OkBtn.UseVisualStyleBackColor = true;
@@ -133,7 +133,7 @@ partial class MainForm
         // startTimePicker
         // 
         startTimePicker.Dock = DockStyle.Fill;
-        startTimePicker.Location = new Point(3, 154);
+        startTimePicker.Location = new Point(3, 32);
         startTimePicker.Margin = new Padding(3, 2, 3, 2);
         startTimePicker.Name = "startTimePicker";
         startTimePicker.Size = new Size(119, 23);
@@ -142,7 +142,7 @@ partial class MainForm
         // endTimePicker
         // 
         endTimePicker.Dock = DockStyle.Fill;
-        endTimePicker.Location = new Point(128, 154);
+        endTimePicker.Location = new Point(128, 32);
         endTimePicker.Margin = new Padding(3, 2, 3, 2);
         endTimePicker.Name = "endTimePicker";
         endTimePicker.Size = new Size(119, 23);

+ 1 - 3
SnapshotWinFormsApp/Repository/TargetRepository.cs

@@ -20,10 +20,8 @@ public class ConcurrentQueueRepository<TEntity> where TEntity : Entity, new()
     public void Insert(TEntity entity, ISqlSugarClient sugarClient, bool isEnd)
     {
         _queue.Enqueue(entity);
-        if (_queue.Count != 100 && isEnd == false) return;
-        sugarClient.Ado.BeginTran();
+        if (_queue.Count != 1000 && isEnd == false) return;
         sugarClient.Insertable(_queue.ToList()).ExecuteCommand();
-        sugarClient.Ado.CommitTran();
         _queue.Clear();
     }
 }

+ 39 - 5
SnapshotWinFormsApp/Tools/MapsterConfig.cs

@@ -12,12 +12,37 @@ public static class MapsterConfig
 {
     public static void RegisterMappings()
     {
+        TypeAdapterConfig<SSP_Volunteer, VolunteerReport>.NewConfig()
+            .Map(m => m.JobType, n => n.SV_WorkType)
+            .Map(m => m.Name, n => n.SV_Name)
+            .Map(m => m.PhoneNumber, n => n.SV_Tel)
+            .Map(m => m.VolunteerPhone, n => n.SV_Tel)
+            .Map(m => m.Volunteer, n => n.SV_DeclareUserName)
+            .Map(m => m.DeclarePhoneNumber, n => n.SV_DeclareUserTel)
+            .Map(m => m.IsDeclare, n => n.SV_ISDeclare == "是" ? true : false)
+            .Map(m => m.Address, n => n.SV_Address2)
+            .Map(m => m.FullAddress, n => n.SV_Address2 + n.SV_Address)
+            .Map(m => m.Latitude, n => ParseLatitude(n.SV_latLon))
+            .Map(m => m.Longitude, n => ParseLongitude(n.SV_latLon))
+            .Map(m => m.IsApprovalProcess, n => n.SV_InspectionItems1 == "是" ? true : false)
+            .Map(m => m.IsProfessionalCertificate, n => n.SV_InspectionItems2 == "是" ? true : false)
+            .Map(m => m.IsSiteMonitoring, n => n.SV_InspectionItems3 == "是" ? true : false)
+            .Map(m => m.IsFireWork, n => n.SV_InspectionItems4 == "是" ? true : false)
+            .Map(m => m.IsClearSafety, n => n.SV_InspectionItems5 == "是" ? true : false)
+            .Map(m => m.HasFireEquipment, n => n.SV_InspectionItems6 == "是" ? true : false)
+            .Map(m => m.IsToolSafety, n => n.SV_InspectionItems7 == "是" ? true : false);
+
+        TypeAdapterConfig<SSP_VolunteerList, Volunteer>.NewConfig()
+            .Map(m => m.Name, n => n.SVL_Name)
+            .Map(m => m.PhoneNumber, n => n.SVL_Tel);
+
         TypeAdapterConfig<SSP_AreaUserEntity, Practitioner>.NewConfig()
             .Map(m => m.CreationTime, n => n.InsertTime.ObjToDate())
             .Map(m => m.PhoneNumber, n => n.Tel)
             .Map(m => m.Street, n => n.AreaTown)
             .Map(m => m.SystemAreaName, n => n.AreaName)
-            .Map(m => m.Gender, n => n.Sex.Trim() == "男" ? EGender.Male : EGender.Female);
+            .Map(m => m.Gender, n => n.Sex.Trim() == "男" ? EGender.Male : EGender.Female)
+            .Ignore(m => m.Id);
 
         TypeAdapterConfig<WeChatUserEntity, ThirdAccount>.NewConfig()
             .Map(m => m.UserName, n => n.WUR_WebUserName)
@@ -51,9 +76,18 @@ public static class MapsterConfig
             .Map(m => m.OrgName, n => n.SIC_BMName)
             .Map(m => m.QRCodeUrl, n => n.SIC_imgUrl);
 
-        TypeAdapterConfig<SSP_InviteLogEntity, InviteCodeRecord>.NewConfig()
-            .Map(m => m.WXOpenId, n => n.SSPI_Openid)
-            .Map(m => m.InviteCode, n => n.SSPI_Code)
-            .Map(m => m.CreationTime, n => n.SSPI_AddTime);
+        TypeAdapterConfig<OldInviteLogEntity, InviteCodeRecord>.NewConfig()
+            .Map(m => m.CreationTime, n => n.CreationTime.ObjToDate())
+            .Ignore(m => m.Id);
+    }
+
+    private static double ParseLatitude(string latLon)
+    {
+        return double.Parse(latLon.Split(',')[0]);
+    }
+
+    private static double ParseLongitude(string latLon)
+    {
+        return double.Parse(latLon.Split(',')[1]);
     }
 }

+ 10 - 2
SnapshotWinFormsApp/Tools/MyExtensions.cs

@@ -1,5 +1,6 @@
 using Abp.Extensions;
 using Newtonsoft.Json;
+using SqlSugar;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.Data;
 using System.Reflection;
@@ -10,6 +11,13 @@ namespace SnapshotWinFormsApp.Tools;
 
 public static class MyExtensions
 {
+    public static string GetObjectValues<T>(this T obj)
+    {
+        return string.Join(",", obj.GetType()
+            .GetProperties(BindingFlags.Public | BindingFlags.Instance)
+            .Select(p => p.GetValue(obj)?.ToString())
+            .Where(m => m != null));
+    }
     public static string ToJson(this object obj)
     {
         return obj == null ? default(string) : JsonConvert.SerializeObject(obj);
@@ -45,8 +53,8 @@ public static class MyExtensions
 
     public static string GetTableName<T>(this T value) where T : class
     {
-        var tableAttribute = typeof(T).GetCustomAttribute<TableAttribute>();
-        return tableAttribute?.Name ?? string.Empty;
+        var tableAttribute = typeof(T).GetCustomAttribute<SugarTable>();
+        return tableAttribute?.TableName ?? string.Empty;
     }
 
     /// <summary>