Skip to content

An hours entry and invoice builder for freelancers and contractors

Notifications You must be signed in to change notification settings

adamfoneil/LiteInvoice3

Repository files navigation

Tip: Some of the links below are broken because a lot of this is in flux, and I haven't had time to fix the links below. Check out the wiki on my Blazor WASM + Server hybrid approach.


I do a little bit of freelance work and need to create invoices. There are invoicing solutions out there, and Billdu comes closest to what I want. But I can't justify a monthly subscription. I've used PayPal invoicing. I'm not wild about its UX, but tracking and receiving payment is easy. One of my two customers doesn't use PayPal, however, so I need a system that can use PayPal but not depend on it. I tried Square, but I couldn't get past their identity verification step. They didn't believe I was who I said I was.

I'd like a single unified interface for entering hours and creating invoices that the customer can view without a login, and offer payment links. I'd like to connect or plug in several different payment providers -- PayPal, Stripe, and who knows what else. One customer uses snail mail, so simply having my mailing address on the invoice works.

So, this a Blazor Server app using a Postgres backend to enter hours and create invoices. I'm using DigitalOcean to host the database, and there's no local database option currently. I am using standard EF migrations to manage the database, so I think in theory you could create the database yourself locally.

Another reason I make apps like this is to refine my own patterns and techniques. I've made several Blazor apps, but I'm always trying to simplify and improve things, and this project is no different in that regard.

  • CRUD operations I've made peace with EF migrations for managing the database schema, but I still don't enjoy EF for CRUD operations.

    • I'm using Dapper.Entities for CRUD, in particular the PostgreSql package.
    • Entity classes are here. The "root" object or the closest thing to an EF DbContext is the DapperEntities object itself. This is added to the DI container at startup.
    • There is an EF ApplicationDbContext, but only for enabling migrations.
    • For consistency of some entity properties, I use a BaseTable.
    • For consistency of insert/update behavior, I use a BaseRepository. This is where I apply the user's time zone in order to set insert/update timestamps.
    • All of my pages/components that need data access inherit from DataComponent. This is how the current user is set for auditing purposes. Auditing requires a little bit of startup code to set the CurrentUserName on the Database. It's during DataComponent initialization however that user info is actually queried -- which gives us the proper ApplicationUser object. This is how I get access to user info in the BaseRepository.
  • SQL queries I use Dapper.QX for SQL queries, and query objects are in Queries. You might think I'm crazy to do SQL like this, but I like having full control over SQL. I appreciate what EF tries to do, but I find that its abstractions simply get in the way, sorry.

  • UI in progress Check out pages in the Setup folder to see what I have working so far.

    • I'm using Radzen components. I have a library of a few reusable elements in my own Radzen.Components project -- this is stuff I got tired of copying and pasting between projects. I might make a NuGet package out of this, but for now it's just a project in the solution. This has common grid save/delete/edit/cancel buttons in GridControls and a few other reusable things along those lines.
    • The one unique thing I'd draw your attention to is GridHelper. This is makes subsequent RadzenGrid setup and operation really compact IMO. See this example ProjectsGrid, which is in use here.
    • Business.razor has example of single form entry.
    • Customers.razor shows a list customer tiles and corresponding entry form.
    • Projects.razor has a grid example.
    • Entries/Index.razor is the main data entry page.