Kaynağa Gözat

Introduce Processor, add AbstractWorkerService, add processor daos

Dalibor Votruba 3 yıl önce
ebeveyn
işleme
3d274298cb
29 değiştirilmiş dosya ile 1529 ekleme ve 181 silme
  1. 421 176
      @Documentation/QLiberace.simp
  2. 1 0
      Common/qdr.app.qlbrc.common/qdr.app.qlbrc.common.csproj
  3. 4 1
      Common/qdr.fnd.core.business/AbstractService.cs
  4. 154 0
      Common/qdr.fnd.core.business/AbstractWorkerService.cs
  5. 10 0
      Common/qdr.fnd.core.business/Enums/WorkerStateEnum.cs
  6. 12 0
      Common/qdr.fnd.core.business/Interface/IWorkerSettings.cs
  7. 50 0
      Common/qdr.fnd.core.business/WorkerStatusInfo.cs
  8. 17 0
      Common/qdr.fnd.core.nlog/LoggerFactory.cs
  9. 1 1
      Common/qdr.fnd.core/Logging/ILogger.cs
  10. 5 2
      Common/qdr.fnd.core/Thread/AbstractWorker.cs
  11. 2 0
      Common/qdr.fnd.core/Thread/LoopWorker.cs
  12. 1 0
      Common/qdr.fnd.core/qdr.fnd.core.csproj
  13. 7 0
      Modules/qdr.app.qlbrc.processor/Class1.cs
  14. 54 0
      Modules/qdr.app.qlbrc.processor/Entities/BaseQueue.cs
  15. 10 0
      Modules/qdr.app.qlbrc.processor/Entities/DaoMapper/BaseQueueDm.cs
  16. 6 0
      Modules/qdr.app.qlbrc.processor/Entities/DoneQueue.cs
  17. 6 0
      Modules/qdr.app.qlbrc.processor/Entities/FailedQueue.cs
  18. 6 0
      Modules/qdr.app.qlbrc.processor/Entities/PendingQueue.cs
  19. 14 0
      Modules/qdr.app.qlbrc.processor/Entities/ProcessingQueue.cs
  20. 38 0
      Modules/qdr.app.qlbrc.processor/Enums/QueueStatusEnum.cs
  21. 22 0
      Modules/qdr.app.qlbrc.processor/qdr.app.qlbrc.processor.csproj
  22. 318 0
      Workbench/Entity/QLiberaceContext.cs
  23. 53 0
      Workbench/Entity/QueueDatum.cs
  24. 68 0
      Workbench/Entity/QueueDone.cs
  25. 68 0
      Workbench/Entity/QueueFailed.cs
  26. 56 0
      Workbench/Entity/QueuePending.cs
  27. 68 0
      Workbench/Entity/QueueProcessing.cs
  28. 49 0
      Workbench/Entity/StatusHistory.cs
  29. 8 1
      qdr.app.qlbrc.sln

Dosya farkı çok büyük olduğundan ihmal edildi
+ 421 - 176
@Documentation/QLiberace.simp


+ 1 - 0
Common/qdr.app.qlbrc.common/qdr.app.qlbrc.common.csproj

@@ -14,6 +14,7 @@
 
   <ItemGroup>
     <Folder Include="Mapper\" />
+    <Folder Include="Services\" />
   </ItemGroup>
 
   <ItemGroup>

+ 4 - 1
Common/qdr.fnd.core.business/AbstractService.cs

@@ -12,6 +12,8 @@ namespace Quadarax.Foundation.Core.Business
         #region *** Properties ***
         protected ILogger<AbstractService> Log { get; }
         protected IContext CurrentContext { get; }
+
+        protected ILoggerFactory LoggerFactory { get; }
         #endregion
 
         #region *** Constructors ***
@@ -21,8 +23,9 @@ namespace Quadarax.Foundation.Core.Business
             if (logger == null)throw new ArgumentNullException(nameof(logger));
             
             Log = logger.CreateLogger<AbstractService>();
+            LoggerFactory = logger;
 
-            //if (currentContext == null)
+                //if (currentContext == null)
             //{
             //    var genericIdentity = new GenericIdentity("genericIdentity");
             //    currentContext = new GenericPrincipal(genericIdentity, new string[] {});

+ 154 - 0
Common/qdr.fnd.core.business/AbstractWorkerService.cs

@@ -0,0 +1,154 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Logging;
+using Quadarax.Foundation.Core.Business.Interface;
+using Quadarax.Foundation.Core.Data.Interface.Domain;
+using Quadarax.Foundation.Core.Data.Interface.Repository;
+using Quadarax.Foundation.Core.Thread;
+using ILogger = Quadarax.Foundation.Core.Logging.ILogger;
+
+namespace Quadarax.Foundation.Core.Business
+{
+    public abstract class AbstractWorkerService<TWorker, TRepository> : AbstractRepositoryService<TRepository> 
+        where TRepository: IRepository
+        where TWorker : AbstractWorker, new()
+    {
+
+        #region *** Private fields ***
+        private IList<Tuple<WorkerStatusInfo,TWorker>> _workers = new List<Tuple<WorkerStatusInfo,TWorker>>();
+        private IWorkerSettings _wrkSettings;
+        private object _lockTransient = null;
+        #endregion
+
+        #region *** Properties ***
+        protected abstract string Name { get; }
+        #endregion
+
+
+        #region *** Constructors ***
+        protected AbstractWorkerService(IWorkerSettings workerSettings, TRepository repository, IContext currentContext, ILoggerFactory logger) : base(repository, currentContext, logger)
+        {
+            _wrkSettings = workerSettings ?? throw new ArgumentNullException(nameof(workerSettings));
+            InitInternal();
+        }
+        #endregion
+
+        #region *** Public Operations ***
+        public void Start()
+        {
+            lock (_lockTransient)
+            {
+                _lockTransient = new object();
+
+                for(var a=0;a<_wrkSettings.WorkerCount;a++)
+                {
+                    var wrk = (TWorker)Activator.CreateInstance(typeof(TWorker),$"{Name}#{a}", new RepoWorkerContext(Repository), (ILogger)LoggerFactory.CreateLogger(this.GetType()));
+                    var info = new WorkerStatusInfo(wrk?.Name);
+                    _workers.Add(new Tuple<WorkerStatusInfo, TWorker>(info, wrk));
+                    wrk?.Start();
+                    info.SetStart();
+                    info.SetIterations(wrk.CurrentThreadId, wrk.IterationCount);
+                }
+
+                _lockTransient = null;
+            }
+        }
+        public void Stop()
+        {
+            lock (_lockTransient)
+            {
+                _lockTransient = new object();
+
+                foreach (var wrk in _workers)
+                {
+                    wrk.Item2.Stop();
+                    wrk.Item1.SetStop();
+                    wrk.Item1.SetIterations(wrk.Item2.CurrentThreadId, wrk.Item2.IterationCount);
+
+                }
+                _lockTransient = null;
+            }
+        }
+
+        #endregion
+
+
+        #region *** Private Operations ***
+        private void InitInternal()
+        {
+        }
+        #endregion
+
+        #region *** Nested Classes ***
+
+        private class RepoWorkerContext : IWorkerContext
+        {
+            public TRepository Repository { get; }
+
+            public RepoWorkerContext(TRepository repository)
+            {
+                Repository = repository ?? throw new ArgumentNullException(nameof(repository));
+            }
+        }
+
+        #endregion
+    }
+
+
+
+    public abstract class AbstractWorkerService<TWorker, TRepository, TRepository1> : AbstractWorkerService<TWorker, TRepository>
+        where TRepository : IRepository
+        where TRepository1 : IRepository
+        where TWorker : AbstractWorker, new()
+    {
+        protected TRepository1 Repository1 { get; }
+        protected AbstractWorkerService(IWorkerSettings workerSettings,TRepository1 repository1, TRepository repository, IContext currentContext, ILoggerFactory logger) : base(workerSettings, repository, currentContext, logger)
+        {
+            Repository1 = repository1 ?? throw new ArgumentNullException(nameof(repository1));
+        }
+    }
+
+    public abstract class AbstractWorkerService<TWorker, TRepository, TRepository1, TRepository2> : AbstractWorkerService<TWorker, TRepository, TRepository1>
+        where TRepository : IRepository
+        where TRepository1 : IRepository
+        where TRepository2 : IRepository
+        where TWorker : AbstractWorker, new()
+    {
+        protected TRepository2 Repository2 { get; }
+        protected AbstractWorkerService(IWorkerSettings workerSettings,TRepository2 repository2, TRepository1 repository1, TRepository repository, IContext currentContext, ILoggerFactory logger) : base(workerSettings,repository1, repository, currentContext, logger)
+        {
+            Repository2 = repository2 ?? throw new ArgumentNullException(nameof(repository2));
+        }
+    }
+
+    public abstract class AbstractWorkerService<TWorker, TRepository, TRepository1, TRepository2, TRepository3> : AbstractWorkerService<TWorker, TRepository, TRepository1, TRepository2>
+        where TRepository : IRepository
+        where TRepository1 : IRepository
+        where TRepository2 : IRepository
+        where TRepository3 : IRepository
+        where TWorker : AbstractWorker, new()
+    {
+        protected TRepository3 Repository3 { get; }
+        protected AbstractWorkerService(IWorkerSettings workerSettings,TRepository3 repository3, TRepository2 repository2, TRepository1 repository1, TRepository repository, IContext currentContext, ILoggerFactory logger) : base(workerSettings, repository2, repository1, repository, currentContext, logger)
+        {
+            Repository3 = repository3 ?? throw new ArgumentNullException(nameof(repository3));
+        }
+    }
+
+    public abstract class AbstractWorkerService<TWorker, TRepository, TRepository1, TRepository2, TRepository3, TRepository4> : AbstractWorkerService<TWorker, TRepository, TRepository1, TRepository2, TRepository3>
+        where TRepository : IRepository
+        where TRepository1 : IRepository
+        where TRepository2 : IRepository
+        where TRepository3 : IRepository
+        where TRepository4 : IRepository
+        where TWorker : AbstractWorker, new()
+    {
+        protected TRepository4 Repository4 { get; }
+        protected AbstractWorkerService(IWorkerSettings workerSettings,TRepository4 repository4, TRepository3 repository3, TRepository2 repository2, TRepository1 repository1, TRepository repository, IContext currentContext, ILoggerFactory logger) : base(workerSettings,repository3,repository2,repository1, repository, currentContext, logger)
+        {
+            Repository4 = repository4 ?? throw new ArgumentNullException(nameof(repository4));
+        }
+    }
+    
+
+}

+ 10 - 0
Common/qdr.fnd.core.business/Enums/WorkerStateEnum.cs

@@ -0,0 +1,10 @@
+namespace Quadarax.Foundation.Core.Business.Enums
+{
+    public enum WorkerStateEnum
+    {
+        Stopped,
+        Starting,
+        Started,
+        Stopping
+    }
+}

+ 12 - 0
Common/qdr.fnd.core.business/Interface/IWorkerSettings.cs

@@ -0,0 +1,12 @@
+using System;
+
+namespace Quadarax.Foundation.Core.Business.Interface
+{
+    public interface IWorkerSettings
+    {
+        TimeSpan Interval { get; }
+        TimeSpan IterationTimeout { get; }
+        TimeSpan StartDelay { get; }
+        int WorkerCount { get; }
+    }
+}

+ 50 - 0
Common/qdr.fnd.core.business/WorkerStatusInfo.cs

@@ -0,0 +1,50 @@
+using Quadarax.Foundation.Core.Business.Enums;
+using System;
+
+namespace Quadarax.Foundation.Core.Business
+{
+    public class WorkerStatusInfo
+    {
+        public string WorkerName { get; private set; }
+        public DateTime LastStart { get;private set;}
+        public DateTime FirstStart { get;private set;}
+        public DateTime? LastStop { get; private set;}
+        public WorkerStateEnum WorkerState { get; private set;}
+        public int ThreadId { get; private set;}
+        public int StartCount { get; private set;}
+        public int StopCount { get; private set;}
+        public long IterationCount { get; private set;}
+
+        public WorkerStatusInfo(string workerName)
+        {
+            WorkerName = workerName;
+            WorkerState = WorkerStateEnum.Starting;
+            FirstStart = DateTime.Now;
+            LastStart = DateTime.Now;
+            StartCount = 0;
+            ThreadId = 0;
+            StopCount = 0;
+            IterationCount = 0;
+        }
+
+        public void SetStop()
+        {
+            StopCount++;
+            LastStop = DateTime.Now;
+            WorkerState = WorkerStateEnum.Stopped;
+        }
+
+        public void SetStart()
+        {
+            StartCount++;
+            LastStart = DateTime.Now;
+            WorkerState = WorkerStateEnum.Started;
+        }
+
+        public void SetIterations(int threadId, long iterationCount)
+        {
+            ThreadId = threadId;
+            IterationCount = iterationCount;
+        }
+    }
+}

+ 17 - 0
Common/qdr.fnd.core.nlog/LoggerFactory.cs

@@ -1,9 +1,11 @@
 using System;
+using Microsoft.Extensions.Logging;
 using NLog;
 using NLog.Config;
 using Quadarax.Foundation.Core.Logging;
 using Quadarax.Foundation.Core.Object;
 using ILogger = Quadarax.Foundation.Core.Logging.ILogger;
+using LogLevel = Microsoft.Extensions.Logging.LogLevel;
 
 namespace Quadarax.Foundation.Core.NLog
 {
@@ -41,5 +43,20 @@ namespace Quadarax.Foundation.Core.NLog
         {
             return new Logger(loggerForType);
         }
+
+        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
+        {
+            throw new NotImplementedException();
+        }
+
+        public bool IsEnabled(LogLevel logLevel)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IDisposable BeginScope<TState>(TState state) where TState : notnull
+        {
+            throw new NotImplementedException();
+        }
     }
 }

+ 1 - 1
Common/qdr.fnd.core/Logging/ILogger.cs

@@ -2,7 +2,7 @@
 
 namespace Quadarax.Foundation.Core.Logging
 {
-    public interface ILogger
+    public interface ILogger : Microsoft.Extensions.Logging.ILogger
     {
         ILog GetLogger(string loggerName);
         ILog GetLogger(Type loggerForType);

+ 5 - 2
Common/qdr.fnd.core/Thread/AbstractWorker.cs

@@ -4,6 +4,7 @@ using System.Threading.Tasks;
 using Quadarax.Foundation.Core.Logging;
 using Quadarax.Foundation.Core.Object;
 using Quadarax.Foundation.Core.Thread.Extensions;
+using Quadarax.Foundation.Core.Value.Extensions;
 
 namespace Quadarax.Foundation.Core.Thread
 {
@@ -24,14 +25,15 @@ namespace Quadarax.Foundation.Core.Thread
         public int CurrentThreadId => System.Threading.Thread.CurrentThread.ManagedThreadId;
         public TimeSpan Timeout { get; set; }
         protected IWorkerContext Context { get; private set; }
+        public long IterationCount { get; protected set; } = 0;
         #endregion
 
         #region *** Constructors ***
-        protected AbstractWorker() : this($"Worker#{Guid.NewGuid():N}", null)
+        protected AbstractWorker() : this($"Worker#{Guid.NewGuid().ToBase32String()}", null)
         {
 
         }
-        protected AbstractWorker(IWorkerContext context) : this($"Worker#{Guid.NewGuid():N}", context)
+        protected AbstractWorker(IWorkerContext context) : this($"Worker#{Guid.NewGuid().ToBase32String()}", context)
         {
 
         }
@@ -133,6 +135,7 @@ namespace Quadarax.Foundation.Core.Thread
                     await wk.Do(wk.Context);
                 else
                     await wk.Do(wk.Context).TimeoutAfter(Timeout);
+                IterationCount++;
             }
             catch (Exception e)
             {

+ 2 - 0
Common/qdr.fnd.core/Thread/LoopWorker.cs

@@ -60,6 +60,8 @@ namespace Quadarax.Foundation.Core.Thread
                     if (_cntLimit > IterationsLimit)
                         exit = true;
                 }
+
+                IterationCount++;
             }
         }
         protected virtual async Task DoIteration(IWorkerContext context)

+ 1 - 0
Common/qdr.fnd.core/qdr.fnd.core.csproj

@@ -15,6 +15,7 @@
   </ItemGroup>
 
   <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
     <PackageReference Include="System.IO.Abstractions" Version="17.2.3" />
   </ItemGroup>
 

+ 7 - 0
Modules/qdr.app.qlbrc.processor/Class1.cs

@@ -0,0 +1,7 @@
+namespace qdr.app.qlbrc.processor
+{
+    public class Class1
+    {
+
+    }
+}

+ 54 - 0
Modules/qdr.app.qlbrc.processor/Entities/BaseQueue.cs

@@ -0,0 +1,54 @@
+using Quadarax.Application.QLiberace.Common.Entities.Base;
+using Quadarax.Application.QLiberace.Processor.Enums;
+
+namespace Quadarax.Application.QLiberace.Processor.Entities
+{
+    public abstract class BaseQueue :  QlbrcEntity, ITenantCode
+    {
+        /// <summary>
+        /// Reference to tenant code that queue belongs
+        /// </summary>
+        public string TenantCode { get; set; }
+        /// <summary>
+        /// Reference to data collection
+        /// </summary>
+        public long DataId { get; set; }
+        /// <summary>
+        /// Reference to status history collection
+        /// </summary>
+        public long StatusHistoryId { get; set; }
+        /// <summary>
+        /// Owner (abstract) key identifier. Usually entity code
+        /// </summary>
+        public string Owner { get; set; } = null!;
+        /// <summary>
+        /// Owner worker identifier who processing task
+        /// </summary>
+        public string AffinityWorker { get; set; } = null!;
+        /// <summary>
+        /// Record status <seealso cref="QueueStatusEnum"/>
+        /// </summary>
+        public QueueStatusEnum Status { get; set; }
+        /// <summary>
+        /// Record processing from datetime
+        /// </summary>
+        public DateTime StartFrom { get; set; }
+        /// <summary>
+        /// Record processing to datetime. If overflow then record is expired.
+        /// </summary>
+        public DateTime? StartTo { get; set; }
+        /// <summary>
+        /// Last status change timestamp
+        /// </summary>
+        public DateTime LastStatusChanged { get; set; }
+        /// <summary>
+        /// Record created timestamp
+        /// </summary>
+        public DateTime Created { get; set; }
+
+        //public virtual QueueData RootData { get; set; } = null!;
+        //public virtual StatusHistory RootStatusHistory { get; set; } = null!;
+
+
+    }
+}

+ 10 - 0
Modules/qdr.app.qlbrc.processor/Entities/DaoMapper/BaseQueueDm.cs

@@ -0,0 +1,10 @@
+
+using Quadarax.Application.QLiberace.Common.Entities.Base.DaoMapper;
+
+namespace Quadarax.Application.QLiberace.Processor.Entities.DaoMapper
+{
+    public abstract class BaseQueueDm<TQueueEntity>: IdDm<TQueueEntity> where TQueueEntity : BaseQueue
+    {
+        
+    }
+}

+ 6 - 0
Modules/qdr.app.qlbrc.processor/Entities/DoneQueue.cs

@@ -0,0 +1,6 @@
+namespace Quadarax.Application.QLiberace.Processor.Entities
+{
+    public class DoneQueue : BaseQueue
+    {
+    }
+}

+ 6 - 0
Modules/qdr.app.qlbrc.processor/Entities/FailedQueue.cs

@@ -0,0 +1,6 @@
+namespace Quadarax.Application.QLiberace.Processor.Entities
+{
+    internal class FailedQueue : BaseQueue
+    {
+    }
+}

+ 6 - 0
Modules/qdr.app.qlbrc.processor/Entities/PendingQueue.cs

@@ -0,0 +1,6 @@
+namespace Quadarax.Application.QLiberace.Processor.Entities
+{
+    public class PendingQueue : BaseQueue
+    {
+    }
+}

+ 14 - 0
Modules/qdr.app.qlbrc.processor/Entities/ProcessingQueue.cs

@@ -0,0 +1,14 @@
+namespace Quadarax.Application.QLiberace.Processor.Entities
+{
+    public class ProcessingQueue : BaseQueue
+    {
+        /// <summary>
+        /// Record started processing at
+        /// </summary>
+        public DateTime Started { get; set; }
+        /// <summary>
+        /// Record finished at
+        /// </summary>
+        public DateTime? Finished { get; set; }
+    }
+}

+ 38 - 0
Modules/qdr.app.qlbrc.processor/Enums/QueueStatusEnum.cs

@@ -0,0 +1,38 @@
+namespace Quadarax.Application.QLiberace.Processor.Enums
+{
+    public enum QueueStatusEnum
+    {
+        /// <summary>
+        /// Task is active pending to process, not yet assigned to worker. Initial state
+        /// </summary>
+        Pending = 0,
+        /// <summary>
+        /// Task is assigned to worker
+        /// </summary>
+        Assigned = 1,
+        /// <summary>
+        /// Task is processing by worker
+        /// </summary>
+        Working = 2,
+        /// <summary>
+        /// Task is done with success
+        /// </summary>
+        Success = 3,
+        /// <summary>
+        /// Task is done with error
+        /// </summary>
+        Failed = 4,
+        /// <summary>
+        /// Task is expired by error or attempts
+        /// </summary>
+        Expired = 5,
+        /// <summary>
+        /// Task is mark for deletion
+        /// </summary>
+        Deleted = 6,
+        /// <summary>
+        /// Task is inactive, not included for processing or deleting
+        /// </summary>
+        Disabled = 7
+    }
+}

+ 22 - 0
Modules/qdr.app.qlbrc.processor/qdr.app.qlbrc.processor.csproj

@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <RootNamespace>Quadarax.Application.QLiberace.Processor</RootNamespace>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\common\qdr.app.qlbrc.common\qdr.app.qlbrc.common.csproj" />
+    <ProjectReference Include="..\..\Common\qdr.fnd.core.business\qdr.fnd.core.business.csproj" />
+    <ProjectReference Include="..\..\Common\qdr.fnd.core.data\qdr.fnd.core.data.csproj" />
+    <ProjectReference Include="..\qdr.app.qlbrc.base\qdr.app.qlbrc.base.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Folder Include="Services\Static\" />
+    <Folder Include="Workers\" />
+  </ItemGroup>
+
+</Project>

+ 318 - 0
Workbench/Entity/QLiberaceContext.cs

@@ -21,7 +21,13 @@ namespace Workbench.Entity
         public virtual DbSet<Currency> Currencies { get; set; } = null!;
         public virtual DbSet<Customer> Customers { get; set; } = null!;
         public virtual DbSet<CustomerUser> CustomerUsers { get; set; } = null!;
+        public virtual DbSet<QueueDatum> QueueData { get; set; } = null!;
+        public virtual DbSet<QueueDone> QueueDones { get; set; } = null!;
+        public virtual DbSet<QueueFailed> QueueFaileds { get; set; } = null!;
+        public virtual DbSet<QueuePending> QueuePendings { get; set; } = null!;
+        public virtual DbSet<QueueProcessing> QueueProcessings { get; set; } = null!;
         public virtual DbSet<Setting> Settings { get; set; } = null!;
+        public virtual DbSet<StatusHistory> StatusHistories { get; set; } = null!;
         public virtual DbSet<Tenant> Tenants { get; set; } = null!;
         public virtual DbSet<TenantCountry> TenantCountries { get; set; } = null!;
         public virtual DbSet<TenantCurrency> TenantCurrencies { get; set; } = null!;
@@ -249,6 +255,290 @@ namespace Workbench.Entity
                     .HasConstraintName("REL_CUSTOMETUSER_CUSTOMER_ID");
             });
 
+            modelBuilder.Entity<QueueDatum>(entity =>
+            {
+                entity.ToTable("QueueData", "Processor");
+
+                entity.Property(e => e.Id).HasComment("Record identifier. Primary key");
+
+                entity.Property(e => e.Code)
+                    .HasMaxLength(100)
+                    .HasComment("Data code. Main key to search.");
+
+                entity.Property(e => e.IsInput).HasComment("Defines if data is input value");
+
+                entity.Property(e => e.ParentId).HasComment("Reference to master (or group) record");
+
+                entity.Property(e => e.SequenceNo).HasComment("Ordinal sequence number of processing related records");
+
+                entity.Property(e => e.Value)
+                    .HasMaxLength(500)
+                    .HasComment("Data value");
+
+                entity.Property(e => e.ValueTypeQualified)
+                    .HasMaxLength(500)
+                    .HasComment("Qualified name of value type");
+
+                entity.HasOne(d => d.Parent)
+                    .WithMany(p => p.InverseParent)
+                    .HasForeignKey(d => d.ParentId)
+                    .OnDelete(DeleteBehavior.ClientSetNull)
+                    .HasConstraintName("REL_DATA_DATA_ID");
+            });
+
+            modelBuilder.Entity<QueueDone>(entity =>
+            {
+                entity.ToTable("QueueDone", "Processor");
+
+                entity.Property(e => e.Id).HasComment("Record identifier. Primary key");
+
+                entity.Property(e => e.AffinityWorker)
+                    .HasMaxLength(64)
+                    .HasComment("Owner worker identifier who processing task");
+
+                entity.Property(e => e.Attempt).HasComment("Attempt counter to process task");
+
+                entity.Property(e => e.Created)
+                    .HasColumnType("datetime")
+                    .HasDefaultValueSql("(getdate())")
+                    .HasComment("Record created timestamp");
+
+                entity.Property(e => e.DataId).HasComment("Reference to data collection");
+
+                entity.Property(e => e.Finished)
+                    .HasColumnType("datetime")
+                    .HasComment("Record finished at");
+
+                entity.Property(e => e.LastStatusChanged)
+                    .HasColumnType("datetime")
+                    .HasDefaultValueSql("(getdate())")
+                    .HasComment("Last status change timestamp");
+
+                entity.Property(e => e.Owner)
+                    .HasMaxLength(50)
+                    .HasComment("Owner (abstract) key identifier. Usually entity code");
+
+                entity.Property(e => e.StartFrom)
+                    .HasColumnType("datetime")
+                    .HasComment("Record processing from datetime");
+
+                entity.Property(e => e.StartTo)
+                    .HasColumnType("datetime")
+                    .HasComment("Record processing to datetime. If overflow then record is expired.");
+
+                entity.Property(e => e.Started)
+                    .HasColumnType("datetime")
+                    .HasDefaultValueSql("(getdate())")
+                    .HasComment("Record started processing at");
+
+                entity.Property(e => e.Status).HasComment("Record status (Pending-0,Assigned-1,Working-2,Success-3,Failed-4,Expired-5)");
+
+                entity.Property(e => e.StatusHistoryId).HasComment("Reference to status history collection");
+
+                entity.Property(e => e.TenantCode)
+                    .HasMaxLength(20)
+                    .HasComment("Reference to tenant code that queue belongs");
+
+                entity.HasOne(d => d.Data)
+                    .WithMany(p => p.QueueDones)
+                    .HasForeignKey(d => d.DataId)
+                    .OnDelete(DeleteBehavior.ClientSetNull)
+                    .HasConstraintName("REL_QDONE_DATA_ID");
+
+                entity.HasOne(d => d.StatusHistory)
+                    .WithMany(p => p.QueueDones)
+                    .HasForeignKey(d => d.StatusHistoryId)
+                    .OnDelete(DeleteBehavior.ClientSetNull)
+                    .HasConstraintName("REL_QDONE_STATUSHISTORY_ID");
+            });
+
+            modelBuilder.Entity<QueueFailed>(entity =>
+            {
+                entity.ToTable("QueueFailed", "Processor");
+
+                entity.Property(e => e.Id).HasComment("Record identifier. Primary key");
+
+                entity.Property(e => e.AffinityWorker)
+                    .HasMaxLength(64)
+                    .HasComment("Owner worker identifier who processing task");
+
+                entity.Property(e => e.Attempt).HasComment("Attempt counter to process task");
+
+                entity.Property(e => e.Created)
+                    .HasColumnType("datetime")
+                    .HasDefaultValueSql("(getdate())")
+                    .HasComment("Record created timestamp");
+
+                entity.Property(e => e.DataId).HasComment("Reference to data collection");
+
+                entity.Property(e => e.Finished)
+                    .HasColumnType("datetime")
+                    .HasComment("Record finished at");
+
+                entity.Property(e => e.LastStatusChanged)
+                    .HasColumnType("datetime")
+                    .HasDefaultValueSql("(getdate())")
+                    .HasComment("Last status change timestamp");
+
+                entity.Property(e => e.Owner)
+                    .HasMaxLength(50)
+                    .HasComment("Owner (abstract) key identifier. Usually entity code");
+
+                entity.Property(e => e.StartFrom)
+                    .HasColumnType("datetime")
+                    .HasComment("Record processing from datetime");
+
+                entity.Property(e => e.StartTo)
+                    .HasColumnType("datetime")
+                    .HasComment("Record processing to datetime. If overflow then record is expired.");
+
+                entity.Property(e => e.Started)
+                    .HasColumnType("datetime")
+                    .HasDefaultValueSql("(getdate())")
+                    .HasComment("Record started processing at");
+
+                entity.Property(e => e.Status).HasComment("Record status (Pending-0,Assigned-1,Working-2,Success-3,Failed-4,Expired-5)");
+
+                entity.Property(e => e.StatusHistoryId).HasComment("Reference to status history collection");
+
+                entity.Property(e => e.TenantCode)
+                    .HasMaxLength(20)
+                    .HasComment("Reference to tenant code that queue belongs");
+
+                entity.HasOne(d => d.Data)
+                    .WithMany(p => p.QueueFaileds)
+                    .HasForeignKey(d => d.DataId)
+                    .OnDelete(DeleteBehavior.ClientSetNull)
+                    .HasConstraintName("REL_QFAILED_DATA_ID");
+
+                entity.HasOne(d => d.StatusHistory)
+                    .WithMany(p => p.QueueFaileds)
+                    .HasForeignKey(d => d.StatusHistoryId)
+                    .OnDelete(DeleteBehavior.ClientSetNull)
+                    .HasConstraintName("REL_QFAILED_STATUSHISTORY_ID");
+            });
+
+            modelBuilder.Entity<QueuePending>(entity =>
+            {
+                entity.ToTable("QueuePending", "Processor");
+
+                entity.Property(e => e.Id).HasComment("Record identifier. Primary key");
+
+                entity.Property(e => e.AffinityWorker)
+                    .HasMaxLength(64)
+                    .HasComment("Owner worker identifier who processing task");
+
+                entity.Property(e => e.Created)
+                    .HasColumnType("datetime")
+                    .HasDefaultValueSql("(getdate())")
+                    .HasComment("Record created timestamp");
+
+                entity.Property(e => e.DataId).HasComment("Reference to data collection");
+
+                entity.Property(e => e.LastStatusChanged)
+                    .HasColumnType("datetime")
+                    .HasDefaultValueSql("(getdate())")
+                    .HasComment("Last status change timestamp");
+
+                entity.Property(e => e.Owner)
+                    .HasMaxLength(50)
+                    .HasComment("Owner (abstract) key identifier. Usually entity code");
+
+                entity.Property(e => e.StartFrom)
+                    .HasColumnType("datetime")
+                    .HasComment("Record processing from datetime");
+
+                entity.Property(e => e.StartTo)
+                    .HasColumnType("datetime")
+                    .HasComment("Record processing to datetime. If overflow then record is expired.");
+
+                entity.Property(e => e.Status).HasComment("Record status (Pending-0,Assigned-1,Working-2,Success-3,Failed-4,Expired-5)");
+
+                entity.Property(e => e.StatusHistoryId).HasComment("Reference to status history collection");
+
+                entity.Property(e => e.TenantCode)
+                    .HasMaxLength(20)
+                    .HasComment("Reference to tenant code that queue belongs");
+
+                entity.HasOne(d => d.Data)
+                    .WithMany(p => p.QueuePendings)
+                    .HasForeignKey(d => d.DataId)
+                    .OnDelete(DeleteBehavior.ClientSetNull)
+                    .HasConstraintName("REL_QPENDING_DATA_ID");
+
+                entity.HasOne(d => d.StatusHistory)
+                    .WithMany(p => p.QueuePendings)
+                    .HasForeignKey(d => d.StatusHistoryId)
+                    .OnDelete(DeleteBehavior.ClientSetNull)
+                    .HasConstraintName("REL_QPENDING_STATUSHISTORY_ID");
+            });
+
+            modelBuilder.Entity<QueueProcessing>(entity =>
+            {
+                entity.ToTable("QueueProcessing", "Processor");
+
+                entity.Property(e => e.Id).HasComment("Record identifier. Primary key");
+
+                entity.Property(e => e.AffinityWorker)
+                    .HasMaxLength(64)
+                    .HasComment("Owner worker identifier who processing task");
+
+                entity.Property(e => e.Attempt).HasComment("Attempt counter to process task");
+
+                entity.Property(e => e.Created)
+                    .HasColumnType("datetime")
+                    .HasDefaultValueSql("(getdate())")
+                    .HasComment("Record created timestamp");
+
+                entity.Property(e => e.DataId).HasComment("Reference to data collection");
+
+                entity.Property(e => e.Finished)
+                    .HasColumnType("datetime")
+                    .HasComment("Record finished at");
+
+                entity.Property(e => e.LastStatusChanged)
+                    .HasColumnType("datetime")
+                    .HasDefaultValueSql("(getdate())")
+                    .HasComment("Last status change timestamp");
+
+                entity.Property(e => e.Owner)
+                    .HasMaxLength(50)
+                    .HasComment("Owner (abstract) key identifier. Usually entity code");
+
+                entity.Property(e => e.StartFrom)
+                    .HasColumnType("datetime")
+                    .HasComment("Record processing from datetime");
+
+                entity.Property(e => e.StartTo)
+                    .HasColumnType("datetime")
+                    .HasComment("Record processing to datetime. If overflow then record is expired.");
+
+                entity.Property(e => e.Started)
+                    .HasColumnType("datetime")
+                    .HasDefaultValueSql("(getdate())")
+                    .HasComment("Record started processing at");
+
+                entity.Property(e => e.Status).HasComment("Record status (Pending-0,Assigned-1,Working-2,Success-3,Failed-4,Expired-5)");
+
+                entity.Property(e => e.StatusHistoryId).HasComment("Reference to status history collection");
+
+                entity.Property(e => e.TenantCode)
+                    .HasMaxLength(20)
+                    .HasComment("Reference to tenant code that queue belongs");
+
+                entity.HasOne(d => d.Data)
+                    .WithMany(p => p.QueueProcessings)
+                    .HasForeignKey(d => d.DataId)
+                    .OnDelete(DeleteBehavior.ClientSetNull)
+                    .HasConstraintName("REL_QPROCESSING_DATA_ID");
+
+                entity.HasOne(d => d.StatusHistory)
+                    .WithMany(p => p.QueueProcessings)
+                    .HasForeignKey(d => d.StatusHistoryId)
+                    .OnDelete(DeleteBehavior.ClientSetNull)
+                    .HasConstraintName("REL_QPROCESSING_STATUSHISTORY_ID");
+            });
+
             modelBuilder.Entity<Setting>(entity =>
             {
                 entity.ToTable("Setting", "base");
@@ -309,6 +599,34 @@ namespace Workbench.Entity
                     .HasConstraintName("REL_SETTING_TENANT_ID");
             });
 
+            modelBuilder.Entity<StatusHistory>(entity =>
+            {
+                entity.ToTable("StatusHistory", "Processor");
+
+                entity.Property(e => e.Id).HasComment("Record identifier. Primary key");
+
+                entity.Property(e => e.Attempt)
+                    .HasDefaultValueSql("((0))")
+                    .HasComment("Actual attempt counter to process task");
+
+                entity.Property(e => e.Created)
+                    .HasColumnType("datetime")
+                    .HasDefaultValueSql("(getdate())")
+                    .HasComment("Record created timestamp");
+
+                entity.Property(e => e.ErrorMessage).HasComment("Last error message");
+
+                entity.Property(e => e.ParentId).HasComment("Reference to master (or group) record");
+
+                entity.Property(e => e.Status).HasComment("Record status (Pending-0,Assigned-1,Working-2,Success-3,Failed-4,Expired-5)");
+
+                entity.HasOne(d => d.Parent)
+                    .WithMany(p => p.InverseParent)
+                    .HasForeignKey(d => d.ParentId)
+                    .OnDelete(DeleteBehavior.ClientSetNull)
+                    .HasConstraintName("REL_STATUSHISTORY_STATUSHISTORY_ID");
+            });
+
             modelBuilder.Entity<Tenant>(entity =>
             {
                 entity.ToTable("Tenant", "base");

+ 53 - 0
Workbench/Entity/QueueDatum.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+
+namespace Workbench.Entity
+{
+    public partial class QueueDatum
+    {
+        public QueueDatum()
+        {
+            InverseParent = new HashSet<QueueDatum>();
+            QueueDones = new HashSet<QueueDone>();
+            QueueFaileds = new HashSet<QueueFailed>();
+            QueuePendings = new HashSet<QueuePending>();
+            QueueProcessings = new HashSet<QueueProcessing>();
+        }
+
+        /// <summary>
+        /// Record identifier. Primary key
+        /// </summary>
+        public long Id { get; set; }
+        /// <summary>
+        /// Reference to master (or group) record
+        /// </summary>
+        public long ParentId { get; set; }
+        /// <summary>
+        /// Data code. Main key to search.
+        /// </summary>
+        public string Code { get; set; } = null!;
+        /// <summary>
+        /// Defines if data is input value
+        /// </summary>
+        public bool IsInput { get; set; }
+        /// <summary>
+        /// Ordinal sequence number of processing related records
+        /// </summary>
+        public int SequenceNo { get; set; }
+        /// <summary>
+        /// Qualified name of value type
+        /// </summary>
+        public string ValueTypeQualified { get; set; } = null!;
+        /// <summary>
+        /// Data value
+        /// </summary>
+        public string? Value { get; set; }
+
+        public virtual QueueDatum Parent { get; set; } = null!;
+        public virtual ICollection<QueueDatum> InverseParent { get; set; }
+        public virtual ICollection<QueueDone> QueueDones { get; set; }
+        public virtual ICollection<QueueFailed> QueueFaileds { get; set; }
+        public virtual ICollection<QueuePending> QueuePendings { get; set; }
+        public virtual ICollection<QueueProcessing> QueueProcessings { get; set; }
+    }
+}

+ 68 - 0
Workbench/Entity/QueueDone.cs

@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+
+namespace Workbench.Entity
+{
+    public partial class QueueDone
+    {
+        /// <summary>
+        /// Record identifier. Primary key
+        /// </summary>
+        public long Id { get; set; }
+        /// <summary>
+        /// Reference to data collection
+        /// </summary>
+        public long DataId { get; set; }
+        /// <summary>
+        /// Reference to status history collection
+        /// </summary>
+        public long StatusHistoryId { get; set; }
+        /// <summary>
+        /// Reference to tenant code that queue belongs
+        /// </summary>
+        public string TenantCode { get; set; } = null!;
+        /// <summary>
+        /// Owner (abstract) key identifier. Usually entity code
+        /// </summary>
+        public string Owner { get; set; } = null!;
+        /// <summary>
+        /// Owner worker identifier who processing task
+        /// </summary>
+        public string AffinityWorker { get; set; } = null!;
+        /// <summary>
+        /// Record status (Pending-0,Assigned-1,Working-2,Success-3,Failed-4,Expired-5)
+        /// </summary>
+        public int Status { get; set; }
+        /// <summary>
+        /// Attempt counter to process task
+        /// </summary>
+        public int Attempt { get; set; }
+        /// <summary>
+        /// Record processing from datetime
+        /// </summary>
+        public DateTime StartFrom { get; set; }
+        /// <summary>
+        /// Record processing to datetime. If overflow then record is expired.
+        /// </summary>
+        public DateTime? StartTo { get; set; }
+        /// <summary>
+        /// Record started processing at
+        /// </summary>
+        public DateTime Started { get; set; }
+        /// <summary>
+        /// Record finished at
+        /// </summary>
+        public DateTime? Finished { get; set; }
+        /// <summary>
+        /// Last status change timestamp
+        /// </summary>
+        public DateTime LastStatusChanged { get; set; }
+        /// <summary>
+        /// Record created timestamp
+        /// </summary>
+        public DateTime Created { get; set; }
+
+        public virtual QueueDatum Data { get; set; } = null!;
+        public virtual StatusHistory StatusHistory { get; set; } = null!;
+    }
+}

+ 68 - 0
Workbench/Entity/QueueFailed.cs

@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+
+namespace Workbench.Entity
+{
+    public partial class QueueFailed
+    {
+        /// <summary>
+        /// Record identifier. Primary key
+        /// </summary>
+        public long Id { get; set; }
+        /// <summary>
+        /// Reference to data collection
+        /// </summary>
+        public long DataId { get; set; }
+        /// <summary>
+        /// Reference to status history collection
+        /// </summary>
+        public long StatusHistoryId { get; set; }
+        /// <summary>
+        /// Reference to tenant code that queue belongs
+        /// </summary>
+        public string TenantCode { get; set; } = null!;
+        /// <summary>
+        /// Owner (abstract) key identifier. Usually entity code
+        /// </summary>
+        public string Owner { get; set; } = null!;
+        /// <summary>
+        /// Owner worker identifier who processing task
+        /// </summary>
+        public string AffinityWorker { get; set; } = null!;
+        /// <summary>
+        /// Record status (Pending-0,Assigned-1,Working-2,Success-3,Failed-4,Expired-5)
+        /// </summary>
+        public int Status { get; set; }
+        /// <summary>
+        /// Attempt counter to process task
+        /// </summary>
+        public int Attempt { get; set; }
+        /// <summary>
+        /// Record processing from datetime
+        /// </summary>
+        public DateTime StartFrom { get; set; }
+        /// <summary>
+        /// Record processing to datetime. If overflow then record is expired.
+        /// </summary>
+        public DateTime? StartTo { get; set; }
+        /// <summary>
+        /// Record started processing at
+        /// </summary>
+        public DateTime Started { get; set; }
+        /// <summary>
+        /// Record finished at
+        /// </summary>
+        public DateTime? Finished { get; set; }
+        /// <summary>
+        /// Last status change timestamp
+        /// </summary>
+        public DateTime LastStatusChanged { get; set; }
+        /// <summary>
+        /// Record created timestamp
+        /// </summary>
+        public DateTime Created { get; set; }
+
+        public virtual QueueDatum Data { get; set; } = null!;
+        public virtual StatusHistory StatusHistory { get; set; } = null!;
+    }
+}

+ 56 - 0
Workbench/Entity/QueuePending.cs

@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+
+namespace Workbench.Entity
+{
+    public partial class QueuePending
+    {
+        /// <summary>
+        /// Record identifier. Primary key
+        /// </summary>
+        public long Id { get; set; }
+        /// <summary>
+        /// Reference to data collection
+        /// </summary>
+        public long DataId { get; set; }
+        /// <summary>
+        /// Reference to status history collection
+        /// </summary>
+        public long StatusHistoryId { get; set; }
+        /// <summary>
+        /// Reference to tenant code that queue belongs
+        /// </summary>
+        public string TenantCode { get; set; } = null!;
+        /// <summary>
+        /// Owner (abstract) key identifier. Usually entity code
+        /// </summary>
+        public string Owner { get; set; } = null!;
+        /// <summary>
+        /// Owner worker identifier who processing task
+        /// </summary>
+        public string AffinityWorker { get; set; } = null!;
+        /// <summary>
+        /// Record status (Pending-0,Assigned-1,Working-2,Success-3,Failed-4,Expired-5)
+        /// </summary>
+        public int Status { get; set; }
+        /// <summary>
+        /// Record processing from datetime
+        /// </summary>
+        public DateTime StartFrom { get; set; }
+        /// <summary>
+        /// Record processing to datetime. If overflow then record is expired.
+        /// </summary>
+        public DateTime? StartTo { get; set; }
+        /// <summary>
+        /// Last status change timestamp
+        /// </summary>
+        public DateTime LastStatusChanged { get; set; }
+        /// <summary>
+        /// Record created timestamp
+        /// </summary>
+        public DateTime Created { get; set; }
+
+        public virtual QueueDatum Data { get; set; } = null!;
+        public virtual StatusHistory StatusHistory { get; set; } = null!;
+    }
+}

+ 68 - 0
Workbench/Entity/QueueProcessing.cs

@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+
+namespace Workbench.Entity
+{
+    public partial class QueueProcessing
+    {
+        /// <summary>
+        /// Record identifier. Primary key
+        /// </summary>
+        public long Id { get; set; }
+        /// <summary>
+        /// Reference to data collection
+        /// </summary>
+        public long DataId { get; set; }
+        /// <summary>
+        /// Reference to status history collection
+        /// </summary>
+        public long StatusHistoryId { get; set; }
+        /// <summary>
+        /// Reference to tenant code that queue belongs
+        /// </summary>
+        public string TenantCode { get; set; } = null!;
+        /// <summary>
+        /// Owner (abstract) key identifier. Usually entity code
+        /// </summary>
+        public string Owner { get; set; } = null!;
+        /// <summary>
+        /// Owner worker identifier who processing task
+        /// </summary>
+        public string AffinityWorker { get; set; } = null!;
+        /// <summary>
+        /// Record status (Pending-0,Assigned-1,Working-2,Success-3,Failed-4,Expired-5)
+        /// </summary>
+        public int Status { get; set; }
+        /// <summary>
+        /// Attempt counter to process task
+        /// </summary>
+        public int Attempt { get; set; }
+        /// <summary>
+        /// Record processing from datetime
+        /// </summary>
+        public DateTime StartFrom { get; set; }
+        /// <summary>
+        /// Record processing to datetime. If overflow then record is expired.
+        /// </summary>
+        public DateTime? StartTo { get; set; }
+        /// <summary>
+        /// Record started processing at
+        /// </summary>
+        public DateTime Started { get; set; }
+        /// <summary>
+        /// Record finished at
+        /// </summary>
+        public DateTime? Finished { get; set; }
+        /// <summary>
+        /// Last status change timestamp
+        /// </summary>
+        public DateTime LastStatusChanged { get; set; }
+        /// <summary>
+        /// Record created timestamp
+        /// </summary>
+        public DateTime Created { get; set; }
+
+        public virtual QueueDatum Data { get; set; } = null!;
+        public virtual StatusHistory StatusHistory { get; set; } = null!;
+    }
+}

+ 49 - 0
Workbench/Entity/StatusHistory.cs

@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+
+namespace Workbench.Entity
+{
+    public partial class StatusHistory
+    {
+        public StatusHistory()
+        {
+            InverseParent = new HashSet<StatusHistory>();
+            QueueDones = new HashSet<QueueDone>();
+            QueueFaileds = new HashSet<QueueFailed>();
+            QueuePendings = new HashSet<QueuePending>();
+            QueueProcessings = new HashSet<QueueProcessing>();
+        }
+
+        /// <summary>
+        /// Record identifier. Primary key
+        /// </summary>
+        public long Id { get; set; }
+        /// <summary>
+        /// Reference to master (or group) record
+        /// </summary>
+        public long ParentId { get; set; }
+        /// <summary>
+        /// Record status (Pending-0,Assigned-1,Working-2,Success-3,Failed-4,Expired-5)
+        /// </summary>
+        public int Status { get; set; }
+        /// <summary>
+        /// Actual attempt counter to process task
+        /// </summary>
+        public int? Attempt { get; set; }
+        /// <summary>
+        /// Last error message
+        /// </summary>
+        public string? ErrorMessage { get; set; }
+        /// <summary>
+        /// Record created timestamp
+        /// </summary>
+        public DateTime Created { get; set; }
+
+        public virtual StatusHistory Parent { get; set; } = null!;
+        public virtual ICollection<StatusHistory> InverseParent { get; set; }
+        public virtual ICollection<QueueDone> QueueDones { get; set; }
+        public virtual ICollection<QueueFailed> QueueFaileds { get; set; }
+        public virtual ICollection<QueuePending> QueuePendings { get; set; }
+        public virtual ICollection<QueueProcessing> QueueProcessings { get; set; }
+    }
+}

+ 8 - 1
qdr.app.qlbrc.sln

@@ -39,7 +39,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "qdr.fnd.core.test", "Common
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Workbench", "Workbench\Workbench.csproj", "{26FEFB38-30C1-49A0-8454-4F9622E98920}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "qdr.app.qlbrc.customer", "Modules\qdr.app.qlbrc.customer\qdr.app.qlbrc.customer.csproj", "{71741095-9F36-4CC5-9B58-B1183DF92A04}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "qdr.app.qlbrc.customer", "Modules\qdr.app.qlbrc.customer\qdr.app.qlbrc.customer.csproj", "{71741095-9F36-4CC5-9B58-B1183DF92A04}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "qdr.app.qlbrc.processor", "Modules\qdr.app.qlbrc.processor\qdr.app.qlbrc.processor.csproj", "{B9E3C672-0E06-462F-84CA-672E610D8786}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -107,6 +109,10 @@ Global
 		{71741095-9F36-4CC5-9B58-B1183DF92A04}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{71741095-9F36-4CC5-9B58-B1183DF92A04}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{71741095-9F36-4CC5-9B58-B1183DF92A04}.Release|Any CPU.Build.0 = Release|Any CPU
+		{B9E3C672-0E06-462F-84CA-672E610D8786}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{B9E3C672-0E06-462F-84CA-672E610D8786}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{B9E3C672-0E06-462F-84CA-672E610D8786}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{B9E3C672-0E06-462F-84CA-672E610D8786}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -127,6 +133,7 @@ Global
 		{3F99AB71-EB24-47D4-BBB6-755ED785D0FE} = {D8C71782-99B7-49FA-BF63-F40D8232B8DE}
 		{0D88D15B-DC73-43F2-8661-ECAAF8EEB173} = {F528683A-B908-440C-9D94-F4675A3F760E}
 		{71741095-9F36-4CC5-9B58-B1183DF92A04} = {24170366-2A73-4C1E-86D6-DA3C987D304E}
+		{B9E3C672-0E06-462F-84CA-672E610D8786} = {24170366-2A73-4C1E-86D6-DA3C987D304E}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {AB5693A0-BE22-4D85-997A-EC8F74FE9B10}

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor