Skip to content

Repository files navigation

FinMind

Enterprise-Grade Accounting & ERP Backend Built on Clean Architecture

An enterprise-grade accounting backend where double-entry bookkeeping rules are enforced as domain invariants β€” not application-layer afterthoughts.

.NET C# Architecture CQRS Docker CI/CD License


Why FinMind Exists

Unlike traditional CRUD-based portfolio projects, FinMind models accounting rules directly inside the domain to preserve business integrity.

Debits equal credits, or the transaction doesn't exist. That rule β€” along with every other accounting invariant β€” lives inside the domain model itself, enforced by rich entities with private setters and explicit behavior methods.

No application service can accidentally post an unbalanced journal entry β€” the domain itself refuses to transition an entry to Posted unless debits equal credits.

FinMind is a fully working general ledger, sub-ledger, and operations backend: chart of accounts, double-entry journal entries, customers, vendors, products with weighted-average costing, purchases, sales orders, and expense tracking.

All of it is wired together through a strict CQRS pipeline, covered by a real test suite, containerized, and shipped through a three-stage CI/CD pipeline to a live deployment.


Table of Contents


Core Features

πŸ“’ General Ledger & Chart of Accounts Full account lifecycle (Assets, Liabilities, Equity, Revenue, Expenses) with unique code enforcement and activation control.

πŸ’° Double-Entry Journal Engine JournalEntry and JournalEntryLine enforce that every posted entry balances to zero before it can transition out of Draft. Entries move through Draft β†’ Posted β†’ Cancelled/Reversed with no shortcuts around the state machine.

🀝 Accounts Receivable & Payable Customer and Vendor entities are linked directly to dedicated GL accounts, so every sale or purchase has a natural home in the ledger from day one β€” no manual reconciliation required.

πŸ“¦ Inventory with Weighted-Average Costing Product.ReceiveStock recalculates average cost on every incoming stock event:

AverageCost = ((CurrentQty Γ— CurrentAvgCost) + (IncomingQty Γ— PurchasePrice)) / TotalQty

🧾 Purchases, Sales Orders & Expenses End-to-end operational workflows that generate the journal entries behind them automatically, rather than requiring manual bookkeeping on top.

🏒 Multi-Tenant & Fully Auditable Every entity carries TenantId, CreatedAt/By, and UpdatedAt/By, with soft delete instead of destructive writes β€” nothing in the ledger ever silently disappears.

⚑ Domain Events State changes (e.g. an expense being completed) raise domain events for decoupled, delayed side effects instead of tangling handlers together.


Architecture

FinMind follows Clean Architecture (Onion Architecture) strictly β€” dependencies point inward, and the Domain layer has zero knowledge of EF Core, ASP.NET Core, or anything infrastructural.

flowchart TB
    A["Presentation Layer<br/>Controllers β†’ MediatR"] --> B["Application Layer<br/>CQRS Features, Validators"]
    C["Infrastructure Layer<br/>EF Core, Identity, JWT"] --> B
    B --> D["Domain Layer<br/>Entities, Enums, Domain Events"]
    C -.implements interfaces defined by.-> B

    style D fill:#4c1d95,color:#fff
    style B fill:#1e3a8a,color:#fff
    style C fill:#374151,color:#fff
    style A fill:#374151,color:#fff
Loading

The Domain layer stays completely independent of Infrastructure and the API β€” which keeps business rules testable in isolation and reusable behind any interface.

Layer Responsibility
Domain Framework-independent core. Rich, self-validating entities (Account, JournalEntry, Product) that own their own business rules.
Application Vertical-slice CQRS use cases. Depends only on Domain; exposes IUnitOfWork / IRepository<T> abstractions and never touches EF Core directly.
Infrastructure Implements every Application-layer interface: AppDbContext, migrations, ASP.NET Core Identity, JWT issuance, seeders.
WepApi Entry point. Thin controllers map HTTP β†’ MediatR commands/queries; global middleware formats every error as RFC 7807 Problem Details.

The payoff is practical: the entire accounting domain can be tested in complete isolation from the database, and the API layer could be swapped out entirely without touching a single business rule.


Tech Stack

Category Technology
Framework ASP.NET Core 9.0, C# 13
Data Access Entity Framework Core, SQL Server
CQRS / Messaging MediatR
Validation FluentValidation (as a MediatR pipeline behavior)
Auth ASP.NET Core Identity, JWT Bearer Tokens, RBAC
Testing xUnit, Moq, FluentAssertions, MockQueryable.Moq
Docs Swagger / OpenAPI
Containerization Docker, Docker Compose
CI/CD GitHub Actions (3-stage: CI β†’ Docker β†’ Deploy)
Hosting MonsterASP.NET (via MSDeploy)

Project Structure

FinMind/
β”œβ”€β”€ Domain/                     # Enterprise Domain Layer (zero external dependencies)
β”‚   β”œβ”€β”€ Common/                 # BaseEntity, audit contracts, Result<T> wrapper
β”‚   β”œβ”€β”€ Entities/                # Account, Customer, Vendor, Product, JournalEntry...
β”‚   β”œβ”€β”€ Enums/                   # EntryType, JournalEntryStatus, AccountType
β”‚   └── Events/                  # Domain events (e.g. ExpenseCompletedEvent)
β”œβ”€β”€ Application/                # Orchestration & Use-Case Layer
β”‚   β”œβ”€β”€ Common/Behaviours/       # MediatR pipeline behaviors (validation, etc.)
β”‚   β”œβ”€β”€ Features/                # Vertical-slice CQRS (Commands, Queries, DTOs, Handlers)
β”‚   └── Interfaces/              # IUnitOfWork, IRepository, IJwtService
β”œβ”€β”€ Infrastracture/             # Technology & Data-Access Layer
β”‚   β”œβ”€β”€ Identity/                 # ASP.NET Core Identity configuration
β”‚   └── Persistence/              # DbContext, Repositories, Migrations, Seeders
β”œβ”€β”€ WepApi/                      # Presentation / API Hosting Layer
β”‚   β”œβ”€β”€ Controllers/              # Thin routing controllers
β”‚   β”œβ”€β”€ Middlewares/               # Global exception handling
β”‚   └── Program.cs                 # Bootstrap, DI registration, migrations on startup
└── Tests/
    β”œβ”€β”€ Domain.Tests/              # Business-rule and invariant unit tests
    └── Application.Tests/         # Command/query handler tests

Domain-Driven Design in Practice

FinMind avoids the anemic-model trap on purpose. State never mutates through a public setter β€” it mutates through a named, intention-revealing method:

public class Account : BaseEntity
{
    public string Name { get; private set; }
    public string Code { get; private set; }
    public AccountType Type { get; private set; }
    public bool IsActive { get; private set; }

    public void DeActivate()
    {
        // Business rule enforcement happens here, not in a service layer
        IsActive = false;
    }
}
// A journal entry can only be posted if debits equal credits β€” enforced by the domain itself
public Result Post()
{
    if (TotalDebits != TotalCredits)
        return Result.Failure("Journal entry is unbalanced.");

    Status = JournalEntryStatus.Posted;
    return Result.Success();
}

Every command flows through a strict pipeline before it ever reaches a handler:

HTTP Request β†’ Controller β†’ MediatR β†’ ValidationBehavior (FluentValidation)
             β†’ Command Handler β†’ Domain Method β†’ IUnitOfWork.SaveChangesAsync()
             β†’ Result<T> β†’ Problem Details (on failure)

Screenshots & Demo

Live Deployment Pipeline CI/CD Pipeline CI, Docker build, and deploy β€” all three GitHub Actions stages passing on main.

Test Suite Tests Passing 419 tests across the Application and Domain layers, zero failures.

API Surface (Swagger) Swagger UI Every business domain exposed as its own controller group.

Database Schema Database ERD The full relational schema, including ASP.NET Core Identity tables.

Live Demo: http://finmind.runasp.net/


Getting Started

Prerequisites

  • .NET 9 SDK
  • SQL Server (local, container, or remote)
  • (Optional) Docker & Docker Compose

Run Locally

# Clone the repository
git clone https://github.com/<your-username>/FinMind.git
cd FinMind

# Restore dependencies
dotnet restore

# Update the connection string in WepApi/appsettings.json, then apply migrations
dotnet ef database update --project Infrastracture --startup-project WepApi

# Run the API
dotnet run --project WepApi

Migrations and seed data (roles, financial settings) run automatically on startup β€” no manual seeding step required.

Once running, browse to /swagger for the full interactive API documentation.


Running with Docker

FinMind ships with a multi-stage Dockerfile and a ready-to-use docker-compose.yml that spins up both the API and a SQL Server instance:

docker-compose up --build

This starts:

  • sqlserver β€” mcr.microsoft.com/mssql/server:2022-latest, exposed on port 1433 with a persistent named volume
  • webapi β€” built from the local Dockerfile, connected to the SQL Server container via environment variables

The published image is also available on Docker Hub: mazenanter/finmind-api:latest.


Testing

The test suite is built on xUnit + Moq + FluentAssertions, structured with a strict Arrange-Act-Assert convention, and currently sits at 419 passing tests (294 in Application.Tests, 125 in Domain.Tests):

[Fact]
public void DeActivate_ShouldSetIsActiveToFalse()
{
    // Arrange
    var account = new Account("Cash", "1001", AccountType.Assets, 1);

    // Act
    account.DeActivate();

    // Assert
    Assert.False(account.IsActive);
}

What's covered:

  • βœ… Command handler success and failure paths (duplicate codes, missing entities)
  • βœ… FluentValidation boundary checks (empty strings, invalid enums)
  • βœ… Domain event emission on state transitions
  • βœ… Deferred IQueryable<T> LINQ streams via MockQueryable.Moq

Because domain entities are deliberately encapsulated with private setters, the test suite includes a reflection-based SetPrivateProperty helper to configure internal state (e.g. injecting a database-generated Id) without weakening the domain's public contract.

dotnet test

CI/CD Pipeline

FinMind ships through a three-stage GitHub Actions pipeline, fully automated from push to production:

   push to any branch
          β”‚
          β–Ό
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    build passes on main    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    image pushed    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚  CI (ci.yml) β”‚ ─────────────────────────▢ β”‚ Docker Build β”‚ ─────────────────▢ β”‚   Deploy    β”‚
   β”‚ build + test β”‚                            β”‚ (docker.yml) β”‚                    β”‚(deploy.yml) β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    dotnet test on                             pushes image to                    win-x86 build,
    .NET 9 / Ubuntu                            Docker Hub                          MSDeploy to
                                                                                    MonsterASP.NET
  1. CI β€” runs on every push: restores, builds, and executes the full test suite on a .NET 9 / Ubuntu runner with NuGet caching.
  2. Continuous Delivery β€” on a successful main build, builds the Docker image and pushes it to Docker Hub.
  3. Continuous Deployment β€” on a successful image build, compiles a Windows (win-x86) release and deploys it live via MSDeploy.

No manual deployment step exists in this project β€” a merge to main is a production release.


Roadmap

  • Multi-Tenant Architecture
  • LLM-powered financial insights and anomaly detection on posted entries
  • Financial statement generation (Trial Balance, Income Statement, Balance Sheet)
  • Docker image optimization & multi-arch builds

License

This project is licensed under the MIT License.


Built by Mazen Anter β€” Backend .NET Developer

If this project is useful to you, consider giving it a ⭐

About

Enterprise-grade Accounting & ERP Backend built with ASP.NET Core 9, Clean Architecture, CQRS, DDD, EF Core, SQL Server, Docker, CI/CD and Double-Entry Accounting.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages