DEV Community

Rolina Vorster
Rolina Vorster

Posted on

I'm building an invoicing SaaS for South African freelancers & small agencies — here's where I am so far

I wanted a portfolio project that goes beyond CRUD — something with real multi-tenancy, role-based permissions, payment integration, and deployment. So I'm building TracKeee, an invoicing app tailored for South African freelancers and small agencies.

It handles SA VAT (15%), ZAR currency, integrates with Yoco for payments, and complies with POPIA. Still a work in progress, but I've learned enough to share.

The stack

  • Backend: ASP.NET Core MVC (.NET 8)
  • Database: Azure SQL + Entity Framework Core
  • Auth: ASP.NET Core Identity
  • PDFs: QuestPDF
  • Email: Brevo SMTP
  • Payments: Yoco Checkout API
  • Charts: Chart.js
  • Hosting: Azure App Service (South Africa North)

The biggest lesson: plan for teams from day one

I started by building everything around a single user. Each user had their own clients, projects, invoices. Simple and it worked.

Then I realised a small agency needs multiple people sharing the same workspace — an owner, an accountant, a project manager. So I refactored the entire data model from user-based to organisation-based. Every model, every controller, every query changed.

It worked, but it would've been way easier if I'd designed for it from the start.

Centralised permissions saved me hours

I have five roles: Owner, Admin, Manager, Accountant, Employee. Instead of checking roles everywhere, I built one method that controls everything:

public bool HasPermission(OrganizationRole role, string action)
{
    return action switch
    {
        "ManageClients" => role == OrganizationRole.Owner 
            || role == OrganizationRole.Admin 
            || role == OrganizationRole.Manager,
        "ViewFinancials" => role == OrganizationRole.Owner 
            || role == OrganizationRole.Admin 
            || role == OrganizationRole.Accountant,
        "Delete" => role == OrganizationRole.Owner,
        _ => false
    };
}
Enter fullscreen mode Exit fullscreen mode

Every controller and view calls this one method. Adding a new role means updating one file.

I initially used shortcuts like role != Employee but switched to explicit positive checks — because adding a new role would silently inherit all those permissions. Small thing, but the kind of mistake that causes real security issues.

What's working so far

  • Clients, projects, time entries with organisation-level isolation
  • Live start/stop timer in the navbar
  • Invoice generation from uninvoiced time entries with 15% VAT
  • Branded PDF invoices with business profile and payment links
  • Email invoices directly to clients with PDF attached
  • Yoco payment integration — each freelancer uses their own account
  • Dashboard with charts (hours by month, revenue, hours by client)
  • Reports page with date range filtering
  • Search and filtering on all list pages
  • Five team roles with permission-based UI
  • POPIA-compliant legal pages and cookie consent
  • Account lockout and session security

What's next

  • Client portal
  • CSV/Excel exports
  • Activity log
  • Replacing Bootstrap with a custom UI

If you've built multi-tenant SaaS with .NET, I'd love to hear how you handled permissions and tenant isolation.

GitHub · Live demo

Top comments (0)