Three apps, three stacks, one AI-assisted workflow — a practical writeup
TRM — Training Management System
Stack: ASP.NET Core 8 MVC, PostgreSQL, Dapper, ASP.NET Core Identity with custom Dapper stores, QuestPDF, SMTP.
Three roles with real separation: Admin manages users, roles and course requirements; Teacher creates courses, uploads materials, marks attendance; Student enrolls, accesses materials, downloads certificates.
Worth noting:
- Custom Identity stores over Dapper. Identity assumes EF Core. Wiring it to Dapper stores was manual work — the generated starting point needed real correction.
- Certificates generate as PDFs at completion (QuestPDF), driven by attendance-based completion percentage.
- Audit fields on every table (
created_by,created_date,modified_by,modified_date) — decided up front, which meant it never had to be retrofitted. - Repository pattern + service layer, async throughout, DI everywhere.
IMS — Inventory & Order Automation
Stack: .NET 8 MVC, PostgreSQL, Dapper, JWT, Bootstrap 5, QuickBooks Online API (with a mock implementation behind a config flag).
30+ stores ordering from a central warehouse. The whole design problem is concurrency.
- Reservation system: adding to cart reserves stock for 20 minutes. Other stores immediately see reduced availability. Expired reservations get cleaned up automatically. Placing the order converts reservations into an actual inventory reduction.
- Zone-based picking: order lines come out sorted Zone A→B→C, then Lane 1→2→3, so the picker walks one route instead of criss-crossing the warehouse.
QuickBooks:MockMode— build and demo the entire order flow without touching the live QuickBooks API, then flip one flag. Highly recommended pattern for any third-party integration.- Reservation timeout is configuration (
Inventory:ReservationTimeoutMinutes), not a magic number in code.
Easiest way to see it work: log in as store1 and store2 in two browsers and race them for the same item.
WHS — Wholesaler Inventory & Orders
Stack: Next.js 14 (Pages Router), TypeScript, MUI v5, Prisma, PostgreSQL, NextAuth.
Parts, suppliers, customers, purchase and sales orders, invoicing, payments, AR/AP aging.
The design decisions that mattered:
- Nothing posts before completion.
DRAFT → APPROVED → COMPLETED, withCANCELLEDreachable only from DRAFT or APPROVED. Creating an order has no side effects — it just records priced lines.receive/shipruns one transaction that claims the status, adjusts stock, writesStockMovementrows and creates the invoice. Insufficient stock rolls the whole transition back. Because nothing posts early, cancelling never needs a reversal. - Money is
Prisma.Decimal, never a JS float. Tax is computed once at order creation; the invoice inherits that breakdown and never recomputes from a rate at read time. - Invoice numbers come from an
InvoiceCountertable inside the invoice-creating transaction, so concurrent completions can't collide and a rolled-back transition releases its number instead of leaving a gap. - Race-safe by construction: a guarded
updateManyre-asserts the invariant in its WHERE clause and a zero row count is a failure. Never read-then-write. - API conventions are enforced, not suggested: every handler starts with
requireAuth, bodies are validated with zod, every mutation ends inhandleApiError(ZodError→400, Prisma P2002/P2025/P2003→409/404/400, everything else→logged 500), list endpoints always return{ data, total, page, pageSize }. - Tests hit a real database. Vitest runs the actual API handlers against a real PostgreSQL schema (
wholesaler_test), deliberately unmocked, because the transaction and rollback guarantees are the point. Concurrency tests fire two conflicting requests withPromise.alland assert exactly one wins and the loser left no partial writes.
The workflow, unchanged across all three
- Write requirements: modules, features, stack, database.
- Generate a high-level project plan.
- Convert the plan into one focused initial prompt.
- Take the scaffolding + DB scripts, run them on a cloud Postgres, wire the connection string, get to a first successful run.
- Iterate — bugs, UI/UX, features, refactors.
Where AI helped, and where it didn't
Helped a lot: project scaffolding, repository/service boilerplate, Razor and React views, DB schema first drafts, wiring config and DI, test harness setup.
Had to drive it myself:
- Anything involving money or concurrency. Decimal handling, reservation windows, guarded updates, transaction boundaries — I designed and verified all of it.
- Stack selection per problem. ASP.NET + Dapper where I wanted tight SQL control and Identity out of the box; Next.js + Prisma where the UI was the product.
- Security posture. Password policies, lockout, CSRF, security headers, file upload validation, parameterised queries. Generated code ships permissive defaults.
- Knowing when the generated approach was subtly wrong — read-then-write instead of a guarded update is the perfect example. It passes every manual test and fails under load.
Rule of thumb I'd give anyone starting: if a bug in the generated code would cost money or corrupt data, don't accept it until you can explain exactly why it's correct.
Happy to break any of these down further — the WHS order lifecycle and the IMS reservation logic are probably the most reusable.

Comments
Post a Comment