Salesbook
Full-stack sales management system — Spring Boot REST API with PostgreSQL and a Vue 3 + Vuetify dashboard.
Overview
Salesbook is a point-of-sale management system that keeps a store’s customers, products, payments, and orders in sync. The backend is a Spring Boot REST API backed by PostgreSQL (schema managed by Flyway), and the frontend is a Vue 3 + Vuetify single-page app.
The interesting part isn’t the CRUD — it’s the business rules that mutate state across entities (orders moving money and stock) and the engineering practices around them: vertical-slice architecture, optimistic locking, an audit trail, and a CI/CD pipeline that deploys to AWS EC2.
Screenshots



Features
- Customers — CRUD with case-insensitive unique name and a balance that reflects real money movement.
- Products — CRUD with stock control (
stock >= 0). - Payments — increase a customer’s balance; updates and removals adjust it accordingly.
- Orders & Order Items — nested resource; creating an item deducts its price from the customer balance and decrements product stock. Orders below stock fail with
400. - BalanceAdjustment audit trail — every manual balance edit is recorded as an immutable, read-only record.
- Concurrency safety — optimistic locking (
@Version) onCustomerandProduct; conflicts return409 Conflict. - Swagger UI — OpenAPI docs at
/swagger-ui.html.
Business rules
Paymentaddsvaluetocustomer.balanceon create, adjusts by the difference on update, subtracts on delete.OrderItemsubtractspricefrom the balance and decrementsproduct.stockby 1 on create; a price update adjusts the balance by the difference; a delete refunds the price and restocks.- Deleting an
Orderrefunds the balance and restocks all of its items via DB cascade. - Manual balance edits create a
BalanceAdjustmentwithvalue = newBalance - oldBalance(no adjustment when unchanged). - Customer and product names are unique case-insensitively (
lower(name)unique index).
Architecture
Browser ──> Vue 3 + Vuetify (ui/) ──> REST API ──> Spring Boot (api/) ──> PostgreSQL
│
└── Flyway migrations (V1..V3)
Backend features follow a vertical slice pattern — each feature is a self-contained package:
customer/ payment/ product/ order/ orderitem/ adjustment/ shared/
│ │ │ │ │ │ │
└─ entity → repository → service → controller → dto/ exceptions + error handler
- Every service method is
@Transactional(reads arereadOnly). - Centralized exception handling maps errors to
400/404/409. - Monetary fields use
numeric(12, 2)/BigDecimal.
Tech stack
| Layer | Technology |
|---|---|
| Backend | Java 21, Spring Boot 4, Gradle |
| Database | PostgreSQL 18, Flyway migrations |
| Frontend | Vue 3, Vuetify 4, Pinia, Vue Router, TypeScript, Vite, Tailwind CSS |
| API docs | springdoc-openapi (Swagger UI) |
| Infra | Docker Compose (local Postgres), GitHub Actions → AWS EC2 (systemd) |
Testing
- Unit suites per vertical slice with JUnit 5, Mockito (strict stubs) and AssertJ — entity guard tests plus service-layer tests.
- CI runs the full
./gradlew buildon every push tomainand deploys the bootable jar to an EC2 instance via GitHub Actions.
Running locally
Prerequisites: Docker, JDK 21, bun.
# 1. Backend
cd api
cp .env.example .env # fill in DB_URL, DB_USERNAME, DB_PASSWORD
docker compose up -d # start PostgreSQL
./gradlew bootRun # API at http://localhost:8080
# 2. Frontend (new terminal)
cd ui
bun install
bun dev # UI at http://localhost:3000 (proxies /api to :8080)
Swagger UI: http://localhost:8080/swagger-ui.html
Why this project?
It demonstrates the practices that matter on real backend teams: database migrations instead of Hibernate auto-DDL, domain-driven mutations with guarded setters, optimistic locking for concurrent stock/balance updates, audit trails for financial data, and a pipeline that takes code to production automatically.