Appouse.ApprovalOrchestrator 1.0.0

dotnet add package Appouse.ApprovalOrchestrator --version 1.0.0
                    
NuGet\Install-Package Appouse.ApprovalOrchestrator -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Appouse.ApprovalOrchestrator" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Appouse.ApprovalOrchestrator" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Appouse.ApprovalOrchestrator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Appouse.ApprovalOrchestrator --version 1.0.0
                    
#r "nuget: Appouse.ApprovalOrchestrator, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Appouse.ApprovalOrchestrator@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Appouse.ApprovalOrchestrator&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Appouse.ApprovalOrchestrator&version=1.0.0
                    
Install as a Cake Tool

🔐 Appouse.ApprovalOrchestrator

Generic, dynamic approval workflow orchestration library for .NET applications.

.NET License NuGet


Herhangi bir .NET uygulamasına entegre edilebilen, entity-agnostic, generic, çok adımlı, strateji destekli bir onay/approval mekanizması kütüphanesi.

✨ Özellikler

Özellik Açıklama
🎯 Generic Yapı IApprovalEngine<TEntity> — her entity tipi için bağımsız onay akışı
🔄 Çoklu Strateji Sequential, Parallel, Majority, Any, All
📊 9 Durum Draft → Pending → InReview → Approved / Rejected / Cancelled / Recalled / Escalated / Expired
🔍 Query/Check API IsApprovedAsync(), IsPendingAsync(), CanUserApproveAsync(), filtreli sorgu, pagination
👥 Delegasyon Onay adımını başka kullanıcıya devretme
⬆️ Eskalasyon Onay talebini üst makama taşıma
Süre Aşımı Otomatik expiry ile background service
📸 Entity Snapshot Submit anında entity'nin JSON kopyası
📋 Dashboard Özet istatistikler, kategori/entity/workflow bazlı dağılım
🔌 Event System IApprovalEventHandler<TEntity> — bildirim, email, message bus entegrasyonu
💾 Pluggable Storage InMemory (varsayılan) + EF Core (relational DB desteği)
⚙️ Fluent Builder AddApprovalOrchestrator<T>().WithStepResolver<R>().WithPolicy<P>()

📦 Paketler

Paket Açıklama
Appouse.ApprovalOrchestrator Core kütüphane — abstractions, engine, in-memory store
Appouse.ApprovalOrchestrator.EntityFrameworkCore EF Core persistence provider

🚀 Hızlı Başlangıç

1. NuGet Kurulumu

dotnet add package Appouse.ApprovalOrchestrator
dotnet add package Appouse.ApprovalOrchestrator.EntityFrameworkCore  # Opsiyonel — DB persistence

2. DI Kaydı (Program.cs)

// En basit kullanım — InMemory store, default policy
builder.Services.AddApprovalOrchestrator<PurchaseOrder>();

// Tam konfigürasyon — EF Core, custom step resolver, policy, event handler
builder.Services
    .AddApprovalOrchestrator<PurchaseOrder>(options =>
    {
        options.DefaultExpiration = TimeSpan.FromDays(7);
        options.AllowSelfApproval = false;
        options.RequireCommentOnReject = true;
        options.SnapshotEntityOnSubmit = true;
    })
    .WithStepResolver<PurchaseOrderStepResolver>()
    .WithPolicy<PurchaseOrderApprovalPolicy>()
    .WithEventHandler<PurchaseOrderApprovalEvents>()
    .UseEntityFrameworkCore(options =>
    {
        options.UseSqlServer(connectionString);
    });

3. Onay Talebi Oluştur ve Gönder

public class PurchaseOrderService
{
    private readonly IApprovalEngine<PurchaseOrder> _engine;
    private readonly IApprovalQueryService<PurchaseOrder> _queries;

    public PurchaseOrderService(
        IApprovalEngine<PurchaseOrder> engine,
        IApprovalQueryService<PurchaseOrder> queries)
    {
        _engine = engine;
        _queries = queries;
    }

    public async Task<ApprovalRequest<PurchaseOrder>> SubmitForApprovalAsync(PurchaseOrder order)
    {
        return await _engine.CreateAndSubmitAsync(
            entity: order,
            entityId: order.Id.ToString(),
            requestedBy: order.RequestedBy,
            workflowName: "PurchaseOrderApproval",
            category: order.Department,
            priority: order.Amount > 50_000 ? 1 : 0);
    }

    public async Task<bool> IsOrderApprovedAsync(string orderId)
    {
        return await _queries.IsApprovedAsync(orderId);
    }
}

📖 Detaylı Kullanım

Onay Yaşam Döngüsü (State Machine)

┌─────────┐    Submit()    ┌───────────┐
│  Draft  │───────────────>│ InReview  │
└─────────┘                └─────┬─────┘
     ^                          │
     │ Recall()        ┌────────┼────────┐
     │                 │        │        │
┌─────────┐      ┌─────▼──┐ ┌──▼────┐ ┌─▼────────┐
│Recalled │      │Approved│ │Rejected│ │Escalated │
└─────────┘      └────────┘ └───────┘ └──────────┘
                                          │
                 ┌────────┐  ┌───────┐    │
                 │Cancelled│  │Expired│ ◄──┘
                 └────────┘  └───────┘

Onay Stratejileri

public enum ApprovalStrategy
{
    Sequential,   // Adım adım — her adım bitmeden sonraki başlamaz
    Parallel,     // Tümü aynı anda — hepsi onaylamalı
    Majority,     // %50+1 onay yeterli
    Any,          // Tek bir onay yeterli
    All           // Tümü onaylamalı (paralel aktivasyon)
}

Strateji seçimi dinamik yapılabilir:

public class MyPolicy : IApprovalPolicy<PurchaseOrder>
{
    public Task<ApprovalStrategy> DetermineStrategyAsync(PurchaseOrder entity, CancellationToken ct)
    {
        // Komite kararı gereken konular için Majority
        if (entity.Amount > 100_000)
            return Task.FromResult(ApprovalStrategy.Majority);

        // Normal akış — sıralı onay
        return Task.FromResult(ApprovalStrategy.Sequential);
    }
    
    // ... diğer metotlar
}

Dinamik Adım Çözümleme (Step Resolver)

Step resolver, entity'nin durumuna göre dinamik olarak onay adımlarını belirler:

public class PurchaseOrderStepResolver : IApprovalStepResolver<PurchaseOrder>
{
    public Task<List<ApprovalStep>> ResolveStepsAsync(
        PurchaseOrder entity, string entityId, ApprovalContext context, CancellationToken ct)
    {
        var steps = new List<ApprovalStep>();

        // Tutar bazlı dinamik adımlar
        steps.Add(new ApprovalStep
        {
            StepName = "Yönetici Onayı",
            AssignedTo = $"manager-{entity.Department}",
            AssignedRole = "Manager"
        });

        if (entity.Amount > 10_000)
        {
            steps.Add(new ApprovalStep
            {
                StepName = "Direktör Onayı",
                AssignedTo = $"director-{entity.Department}",
                AssignedRole = "Director"
            });
        }

        if (entity.Amount > 100_000)
        {
            steps.Add(new ApprovalStep
            {
                StepName = "CFO Onayı",
                AssignedTo = "cfo",
                AssignedRole = "CFO"
            });
        }

        return Task.FromResult(steps);
    }
}

Onay İşlemleri (Engine)

// Oluştur + Gönder (tek adımda)
var request = await _engine.CreateAndSubmitAsync(entity, entityId, requestedBy);

// Adım onayla
await _engine.ApproveStepAsync(new ApprovalDecisionRequest
{
    ApprovalRequestId = requestId,
    StepId = stepId,
    DecidedBy = "manager1",
    Comment = "Onaylandı"
});

// Adım reddet
await _engine.RejectStepAsync(new ApprovalDecisionRequest
{
    ApprovalRequestId = requestId,
    StepId = stepId,
    DecidedBy = "manager1",
    Comment = "Bütçe aşımı"
});

// Opsiyonel adım atla
await _engine.SkipStepAsync(requestId, stepId, "admin");

// Delegasyon
await _engine.DelegateStepAsync(requestId, stepId, "manager1", "manager2");

// Eskalasyon
await _engine.EscalateAsync(requestId, "manager1", "director1", "Acil onay gerekli");

// İptal
await _engine.CancelAsync(requestId, "admin", "Talep geçersiz");

// Geri çekme (sadece talep eden yapabilir)
await _engine.RecallAsync(requestId, requestedBy);

Query/Check API — Onay Durumu Sorgulama

// ── Hızlı Durum Kontrolleri ──
bool isApproved   = await _queries.IsApprovedAsync(entityId);
bool isPending    = await _queries.IsPendingAsync(entityId);
bool isRejected   = await _queries.IsRejectedAsync(entityId);
bool hasActive    = await _queries.HasActiveRequestAsync(entityId);
var  status       = await _queries.GetStatusAsync(entityId);       // ApprovalStatus?
bool canApprove   = await _queries.CanUserApproveAsync(requestId, userId);

// ── Listeler ──
var myPending     = await _queries.GetPendingForUserAsync(userId);   // Benim onayımı bekleyenler
var myRequests    = await _queries.GetMyRequestsAsync(userId);       // Benim taleplerim
var overdue       = await _queries.GetOverdueAsync();                // Süresi geçenler
var expiringSoon  = await _queries.GetExpiringAsync(TimeSpan.FromHours(24)); // 24 saat içinde dolacaklar

// ── Filtreli Sorgu (Pagination + Sort) ──
var result = await _queries.QueryAsync(new ApprovalFilter
{
    Status = ApprovalStatus.InReview,
    Category = "IT",
    RequestedBy = "user1",
    MinPriority = 1,
    CreatedAfter = DateTime.UtcNow.AddDays(-30),
    PageNumber = 1,
    PageSize = 20,
    SortBy = "CreatedAt",
    SortDescending = true
});

// result.Items — sayfa öğeleri
// result.TotalCount — toplam kayıt
// result.TotalPages — toplam sayfa
// result.HasNextPage / HasPreviousPage

// ── Dashboard Özeti ──
var summary = await _queries.GetSummaryAsync(userId);
// summary.TotalPending, TotalApproved, TotalRejected, AssignedToMe, OverdueCount
// summary.ByCategory, ByEntityType, ByWorkflow — dağılım sözlükleri

// ── Adım Geçmişi (Audit Trail) ──
var steps = await _queries.GetStepHistoryAsync(requestId);

Event Handler — Bildirim Entegrasyonu

public class EmailApprovalHandler : IApprovalEventHandler<PurchaseOrder>
{
    private readonly IEmailService _emailService;

    public EmailApprovalHandler(IEmailService emailService) => _emailService = emailService;

    public async Task OnSubmittedAsync(ApprovalRequest<PurchaseOrder> request, CancellationToken ct)
    {
        var step = request.CurrentStep;
        if (step != null)
        {
            await _emailService.SendAsync(
                to: step.EffectiveAssignee,
                subject: $"Yeni Onay Talebi: {request.EntityId}",
                body: $"Merhaba, {request.RequestedBy} tarafından gönderilen talep onayınızı bekliyor.");
        }
    }

    public async Task OnApprovedAsync(ApprovalRequest<PurchaseOrder> request, CancellationToken ct)
    {
        await _emailService.SendAsync(
            to: request.RequestedBy,
            subject: $"Talebiniz Onaylandı: {request.EntityId}",
            body: "Satın alma talebiniz tüm adımlardan geçerek onaylanmıştır.");
    }

    // ... diğer event'ler benzer şekilde
    public Task OnStepCompletedAsync(ApprovalRequest<PurchaseOrder> r, ApprovalStep s, CancellationToken ct) => Task.CompletedTask;
    public Task OnRejectedAsync(ApprovalRequest<PurchaseOrder> r, CancellationToken ct) => Task.CompletedTask;
    public Task OnCancelledAsync(ApprovalRequest<PurchaseOrder> r, CancellationToken ct) => Task.CompletedTask;
    public Task OnEscalatedAsync(ApprovalRequest<PurchaseOrder> r, CancellationToken ct) => Task.CompletedTask;
    public Task OnExpiredAsync(ApprovalRequest<PurchaseOrder> r, CancellationToken ct) => Task.CompletedTask;
    public Task OnDelegatedAsync(ApprovalRequest<PurchaseOrder> r, ApprovalStep s, CancellationToken ct) => Task.CompletedTask;
    public Task OnRecalledAsync(ApprovalRequest<PurchaseOrder> r, CancellationToken ct) => Task.CompletedTask;
}

Approval Policy — İş Kuralları

public class PurchaseOrderPolicy : IApprovalPolicy<PurchaseOrder>
{
    // 100₺ altı onay gerektirmez
    public Task<bool> RequiresApprovalAsync(PurchaseOrder entity, CancellationToken ct)
        => Task.FromResult(entity.Amount >= 100);

    // Self-approval kontrolü
    public Task<bool> CanSubmitAsync(PurchaseOrder entity, string requestedBy, CancellationToken ct)
        => Task.FromResult(true);

    // Rol bazlı onay yetkisi
    public Task<bool> CanApproveAsync(
        ApprovalRequest<PurchaseOrder> request, ApprovalStep step, string userId, CancellationToken ct)
        => Task.FromResult(step.AssignedRole != null); // Rol varsa herkes onaylayabilir

    // Tutar bazlı strateji
    public Task<ApprovalStrategy> DetermineStrategyAsync(PurchaseOrder entity, CancellationToken ct)
        => Task.FromResult(entity.Amount > 100_000 ? ApprovalStrategy.Majority : ApprovalStrategy.Sequential);

    // Tutar bazlı expire süresi
    public Task<TimeSpan?> GetExpirationAsync(PurchaseOrder entity, CancellationToken ct)
        => Task.FromResult<TimeSpan?>(entity.Amount switch
        {
            < 1_000  => TimeSpan.FromDays(3),
            < 10_000 => TimeSpan.FromDays(7),
            _        => TimeSpan.FromDays(14)
        });
}

🏗️ Çoklu Entity Desteği

Tek uygulamada birden fazla entity tipi için onay sistemi kaydedilebilir:

// Satın Alma Onayı
builder.Services
    .AddApprovalOrchestrator<PurchaseOrder>(opt => opt.RequireCommentOnReject = true)
    .WithStepResolver<PurchaseOrderStepResolver>()
    .WithPolicy<PurchaseOrderPolicy>();

// İzin Talebi Onayı
builder.Services
    .AddApprovalOrchestrator<LeaveRequest>(opt => opt.AllowSelfApproval = false)
    .WithStepResolver<LeaveRequestStepResolver>()
    .WithPolicy<LeaveRequestPolicy>();

// Belge Onayı
builder.Services
    .AddApprovalOrchestrator<Document>()
    .WithStepResolver<DocumentStepResolver>()
    .WithEventHandler<DocumentApprovalNotifier>();

Her entity tipi kendi bağımsız engine, store, policy ve event handler'ına sahiptir.


🗄️ Persistence

InMemory Store (Varsayılan)

// Ekstra bir şey yapmaya gerek yok — varsayılan olarak InMemory kullanılır
builder.Services.AddApprovalOrchestrator<PurchaseOrder>();

⚠️ InMemory store uygulama yeniden başlatıldığında verileri kaybeder. Test ve prototipleme için uygundur.

EF Core (SQL Server, PostgreSQL, MySQL, Oracle, SQLite)

// SQL Server
.UseEntityFrameworkCore(options => options.UseSqlServer(connectionString));

// PostgreSQL
.UseEntityFrameworkCore(options => options.UseNpgsql(connectionString));

// MySQL
.UseEntityFrameworkCore(options => options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));

// SQLite (test)
.UseEntityFrameworkCore(options => options.UseSqlite("Data Source=approvals.db"));

// InMemory EF Core (test)
.UseEntityFrameworkCore(options => options.UseInMemoryDatabase("TestDb"));

EF Core Migration

dotnet ef migrations add InitialApprovals --project src/Appouse.ApprovalOrchestrator.EntityFrameworkCore
dotnet ef database update --project src/Appouse.ApprovalOrchestrator.EntityFrameworkCore

Custom Store

Kendi persistence mekanizmanızı yazabilirsiniz:

public class RedisApprovalStore<TEntity> : IApprovalStore<TEntity> where TEntity : class
{
    // Redis implementasyonu
}

builder.Services
    .AddApprovalOrchestrator<PurchaseOrder>()
    .WithStore<RedisApprovalStore<PurchaseOrder>>();

⚙️ Konfigürasyon Referansı

builder.Services.AddApprovalOrchestrator<TEntity>(options =>
{
    // Varsayılan expire süresi (null = expire yok)
    options.DefaultExpiration = TimeSpan.FromDays(7);

    // Otomatik expire background service
    options.AutoExpireEnabled = true;
    options.AutoExpireCheckInterval = TimeSpan.FromMinutes(30);

    // Self-approval izni
    options.AllowSelfApproval = false;

    // Ret'te yorum zorunluluğu
    options.RequireCommentOnReject = true;

    // Submit anında entity snapshot'ı
    options.SnapshotEntityOnSubmit = true;

    // Maksimum delegasyon derinliği
    options.MaxDelegationDepth = 3;

    // Event handler hatalarında exception fırlatılsın mı
    options.ThrowOnEventHandlerFailure = false;
});

🎯 Kullanım Senaryoları

Senaryo Entity Tipi Step Resolver Mantığı
Satın Alma Onayı PurchaseOrder Tutara göre Manager → Director → CFO
İzin Talebi LeaveRequest Süreye göre Manager → HR
Belge Onayı Document Gizlilik seviyesine göre adımlar
ITSM Değişiklik Yönetimi ChangeRequest Risk seviyesine göre CAB → Director
Masraf Beyanı ExpenseReport Tutara göre Manager → Finance
Proje Teklifi ProjectProposal Bütçeye göre PMO → VP → C-Level
Kredi Başvurusu LoanApplication Skora göre Analyst → Supervisor → Committee
İnsan Kaynakları PromotionRequest Pozisyona göre Manager → HR → VP
Sözleşme Yönetimi Contract Tutara göre Legal → Finance → CEO
Yayınlama ContentPublish İçerik tipine göre Editor → Chief Editor

🧪 Test

# Unit testleri çalıştır
dotnet test Appouse.ApprovalOrchestrator.sln

# Detaylı çıktı
dotnet test --verbosity normal --logger "trx"

📂 Proje Yapısı

ApprovalOrchestrator/
├── src/
│   ├── Appouse.ApprovalOrchestrator/                    # Core library
│   │   ├── Abstractions/                                # Interface tanımları
│   │   │   ├── IApprovalEngine.cs                       # Workflow engine
│   │   │   ├── IApprovalQueryService.cs                 # Query/Check API
│   │   │   ├── IApprovalStore.cs                        # Persistence soyutlaması
│   │   │   ├── IApprovalStepResolver.cs                 # Dinamik adım çözücü
│   │   │   ├── IApprovalPolicy.cs                       # İş kuralları
│   │   │   └── IApprovalEventHandler.cs                 # Event callback
│   │   ├── Configuration/
│   │   │   └── ApprovalOrchestratorOptions.cs           # Konfigürasyon
│   │   ├── Defaults/
│   │   │   ├── DefaultApprovalPolicy.cs                 # Varsayılan politika
│   │   │   ├── DefaultApprovalStepResolver.cs           # Varsayılan resolver
│   │   │   ├── InMemoryApprovalStore.cs                 # InMemory store
│   │   │   └── NullApprovalEventHandler.cs              # No-op event handler
│   │   ├── Engine/
│   │   │   ├── ApprovalEngine.cs                        # Ana engine implementasyonu
│   │   │   └── ApprovalQueryService.cs                  # Query service impl.
│   │   ├── Enums/
│   │   │   ├── ApprovalStatus.cs                        # 9 durum
│   │   │   ├── ApprovalStrategy.cs                      # 5 strateji
│   │   │   └── StepDecision.cs                          # Adım kararları
│   │   ├── Exceptions/
│   │   │   ├── ApprovalException.cs
│   │   │   ├── ApprovalNotFoundException.cs
│   │   │   ├── InvalidApprovalTransitionException.cs
│   │   │   └── UnauthorizedApprovalException.cs
│   │   ├── Extensions/
│   │   │   └── ServiceCollectionExtensions.cs           # DI builder
│   │   ├── Background/
│   │   │   └── ApprovalExpirationHostedService.cs       # Auto-expire service
│   │   └── Models/
│   │       ├── ApprovalRequest.cs                       # Ana model
│   │       ├── ApprovalStep.cs                          # Adım modeli
│   │       ├── ApprovalContext.cs                        # Kontekst
│   │       ├── ApprovalDecisionRequest.cs               # Karar DTO
│   │       ├── ApprovalFilter.cs                        # Filtre modeli
│   │       ├── ApprovalSummary.cs                       # Dashboard özeti
│   │       └── PagedResult.cs                           # Sayfalı sonuç
│   │
│   └── Appouse.ApprovalOrchestrator.EntityFrameworkCore/ # EF Core persistence
│       ├── ApprovalDbContext.cs
│       ├── Entities/
│       ├── Extensions/
│       └── Stores/EfCoreApprovalStore.cs
│
├── samples/
│   └── Appouse.ApprovalOrchestrator.Sample.WebApi/       # Örnek Web API
│       ├── Controllers/PurchaseOrdersController.cs
│       ├── Models/PurchaseOrder.cs
│       ├── Workflows/
│       │   ├── PurchaseOrderStepResolver.cs
│       │   ├── PurchaseOrderPolicy.cs
│       │   └── PurchaseOrderApprovalEvents.cs
│       └── Program.cs
│
├── tests/
│   └── Appouse.ApprovalOrchestrator.UnitTests/
│       ├── ApprovalEngineTests.cs
│       ├── ApprovalQueryServiceTests.cs
│       └── ApprovalStrategyTests.cs
│
├── Appouse.ApprovalOrchestrator.sln
├── Directory.Build.props
├── Directory.Packages.props
├── pack.bat
├── LICENSE
└── README.md

🔧 Sample Web API Çalıştırma

cd samples/Appouse.ApprovalOrchestrator.Sample.WebApi
dotnet run

Swagger UI: http://localhost:5000 (veya otomatik atanan port)

Örnek API Akışı

1. Satın Alma Talebi Oluştur:

curl -X POST http://localhost:5000/api/purchase-orders \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Yeni Sunucu",
    "description": "Geliştirme ortamı için sunucu",
    "amount": 15000,
    "currency": "TRY",
    "department": "IT",
    "requestedBy": "user1",
    "vendor": "Dell"
  }'

2. Onay Durumunu Kontrol Et:

curl http://localhost:5000/api/purchase-orders/{entityId}/status
curl http://localhost:5000/api/purchase-orders/{entityId}/is-approved

3. İlk Adımı Onayla (Manager):

curl -X POST http://localhost:5000/api/purchase-orders/{requestId}/approve \
  -H "Content-Type: application/json" \
  -d '{
    "stepId": "{stepId}",
    "decidedBy": "manager-it",
    "comment": "Uygun bulundu"
  }'

4. İkinci Adımı Onayla (Director):

curl -X POST http://localhost:5000/api/purchase-orders/{requestId}/approve \
  -H "Content-Type: application/json" \
  -d '{
    "stepId": "{stepId}",
    "decidedBy": "director-it",
    "comment": "Onaylandı"
  }'

5. Dashboard Özeti:

curl http://localhost:5000/api/approvals/summary?userId=user1

6. Bekleyen Onaylar:

curl http://localhost:5000/api/approvals/pending/manager-it

📋 Sık Sorulan Sorular (FAQ)

<details> <summary><strong>Tek adımlı basit onay için ne yapmalıyım?</strong></summary>

Default step resolver otomatik olarak tek adımlı bir onay oluşturur. Hiçbir özelleştirme yapmanıza gerek yok:

builder.Services.AddApprovalOrchestrator<MyEntity>();

</details>

<details> <summary><strong>Approval gerektirmeyen entity'leri nasıl handle ederim?</strong></summary>

IApprovalPolicy<TEntity>.RequiresApprovalAsync() metodunda false döndürün — engine otomatik olarak Approved durumuna geçer:

public Task<bool> RequiresApprovalAsync(PurchaseOrder entity, CancellationToken ct)
    => Task.FromResult(entity.Amount >= 100); // 100₺ altı otomatik onay

</details>

<details> <summary><strong>Aynı entity için birden fazla aktif talep olabilir mi?</strong></summary>

Evet, varsayılan olarak olabilir. Bunu engellemek için IApprovalPolicy.CanSubmitAsync() içinde kontrol ekleyebilirsiniz:

public async Task<bool> CanSubmitAsync(PurchaseOrder entity, string requestedBy, CancellationToken ct)
{
    var hasActive = await _queries.HasActiveRequestAsync(entity.Id.ToString(), ct);
    return !hasActive; // Aktif talep varsa yeni talep açılamaz
}

</details>

<details> <summary><strong>Metadata alanını ne için kullanabilirim?</strong></summary>

Hem ApprovalRequest hem ApprovalStep üzerinde Dictionary<string, string> Metadata alanı mevcuttur. IP adresi, session ID, departman kodu, external reference gibi özel verileri saklamak için kullanabilirsiniz. </details>

<details> <summary><strong>Custom store nasıl yazarım?</strong></summary>

IApprovalStore<TEntity> interface'ini implement edin (Redis, MongoDB, Cassandra vb.):

public class MongoApprovalStore<TEntity> : IApprovalStore<TEntity> where TEntity : class
{
    // MongoDB implementasyonu
}

builder.Services.AddApprovalOrchestrator<T>().WithStore<MongoApprovalStore<T>>();

</details>


📄 Lisans

MIT License — Detaylar için LICENSE dosyasına bakın.


🤝 Katkıda Bulunma

  1. Fork edin
  2. Feature branch oluşturun (git checkout -b feature/amazing-feature)
  3. Commit atın (git commit -m 'feat: add amazing feature')
  4. Branch'e push edin (git push origin feature/amazing-feature)
  5. Pull Request açın

Appouse — Enterprise-grade .NET libraries 🚀

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Appouse.ApprovalOrchestrator:

Package Downloads
Appouse.ApprovalOrchestrator.EntityFrameworkCore

Entity Framework Core persistence provider for Appouse.ApprovalOrchestrator. Provides EF Core-based storage with full query support.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 124 4/10/2026