Initial import

This commit is contained in:
Flatlogic Bot 2026-03-24 15:28:07 +00:00
commit 29f8d58256
7 changed files with 2497 additions and 0 deletions

157
00_PROJECT_OVERVIEW.md Normal file
View File

@ -0,0 +1,157 @@
# Swish Auto Care — Garage Management System (GMS)
## Master Project Context
> **For Claude Opus Agent:** Read this file first before any other file. This is your north star.
---
## What You Are Building
A **web-based Garage Management System** for Swish Auto Care — an automotive service and car wash studio in India. They currently run everything on paper: job cards, staff attendance, parts sales, billing, and cash management. You are replacing all of that with a clean, functional digital system.
This is a **working demo build** — a single-admin system running locally (localhost). No cloud, no multi-branch, no mobile app. Just a solid, functional demo that proves value to the client.
---
## The Big Picture
```
Admin logs in
Dashboard (today's snapshot: jobs, revenue, staff, cash)
┌─────────────────────────────────────────────────────┐
│ M1: Login & Dashboard │
│ M2: Job Card Management (Full + Quick Wash) │
│ M3: Job Status Board (Active → Done) │
│ M4: Staff Management & Attendance │
│ M5: Staff Salary & Payment │
│ M6: Parts Inventory + POS Sales │
│ M7: Accounts (Consolidated Financial View) │
│ M8: Daily Sales Report (Excel Download) │
│ M9: Cash In / Cash Out Ledger │
│ M10: Data Export to Excel (4 databases) │
└─────────────────────────────────────────────────────┘
```
---
## Tech Stack (Fixed — Do Not Deviate)
| Layer | Technology |
|-------|-----------|
| Frontend | React 18 + TypeScript + Vite + React Router v6 + CSS Modules |
| Backend | Node.js + Express.js |
| Database | MySQL 8.x |
| Excel Export | SheetJS (xlsx npm package) |
| Auth | JWT (jsonwebtoken) + bcrypt |
| HTTP Client | Axios (frontend → backend) |
---
## Project Folder Structure
```
swish-gms/
├── backend/
│ ├── src/
│ │ ├── config/
│ │ │ └── db.js # MySQL connection pool
│ │ ├── middleware/
│ │ │ └── auth.js # JWT verify middleware
│ │ ├── routes/
│ │ │ ├── auth.js
│ │ │ ├── dashboard.js
│ │ │ ├── jobCards.js
│ │ │ ├── staff.js
│ │ │ ├── attendance.js
│ │ │ ├── salary.js
│ │ │ ├── parts.js
│ │ │ ├── partsSales.js
│ │ │ ├── cashLedger.js
│ │ │ ├── accounts.js
│ │ │ └── exports.js
│ │ └── server.js
│ ├── .env
│ └── package.json
└── frontend/
├── src/
│ ├── api/
│ │ └── axios.ts # Axios instance with JWT header
│ ├── components/
│ │ ├── Layout/
│ │ │ ├── Sidebar.tsx
│ │ │ ├── TopBar.tsx
│ │ │ └── Layout.tsx
│ │ └── shared/
│ │ ├── Badge.tsx
│ │ ├── Modal.tsx
│ │ └── Table.tsx
│ ├── pages/
│ │ ├── Login.tsx
│ │ ├── Dashboard.tsx
│ │ ├── JobCards.tsx
│ │ ├── JobCardNew.tsx
│ │ ├── StatusBoard.tsx
│ │ ├── Staff.tsx
│ │ ├── Attendance.tsx
│ │ ├── Salary.tsx
│ │ ├── Parts.tsx
│ │ ├── PartsSales.tsx
│ │ ├── CashLedger.tsx
│ │ ├── Accounts.tsx
│ │ ├── DailyReport.tsx
│ │ └── Exports.tsx
│ ├── context/
│ │ └── AuthContext.tsx
│ ├── App.tsx
│ └── main.tsx
├── index.html
└── package.json
```
---
## Key Business Rules (Never Break These)
1. **Job cards can only go Active → Done. No rollback.**
2. **All financial entries are timestamped and cannot be deleted.** Admin can "void" but not delete.
3. **Salary formula:** `(monthly_salary / 26) × days_present`. Admin can override the final paid amount.
4. **Job card number format:** `SAC-YYYYMMDD-001` (sequential per day).
5. **Staff code format:** `SAC-STF-001` (sequential).
6. **Parts code format:** `SAC-PRT-001` (sequential).
7. **Currency:** Always INR ₹. Indian date format DD/MM/YYYY.
8. **Stock auto-deducts** on parts sale entry.
9. **Parts sales can be linked to a job card** (optional — admin selects).
10. **Demo is single admin only** — no staff login in this scope.
---
## What "Done" Looks Like
The demo passes when all 10 Acceptance Criteria (AC-01 to AC-10) pass:
- AC-01: Admin can log in → dashboard
- AC-02: Admin can create Full and Quick Wash job cards
- AC-03: Jobs show as Active, can be marked Done with payment
- AC-04: Admin can mark daily attendance for all staff
- AC-05: Admin can log salary/advance payment and view history
- AC-06: Admin can record parts sale, stock decreases
- AC-07: Admin can enter cash in/out with reasons
- AC-08: Accounts module shows today's income, expenses, balance
- AC-09: Admin downloads Daily Sales Report as .xlsx
- AC-10: All 4 Excel exports produce correct downloadable files
---
## Reference Files in This Kit
| File | What It Covers |
|------|---------------|
| `00_PROJECT_OVERVIEW.md` | This file — master context |
| `01_DATABASE_SCHEMA.md` | All 7 MySQL tables, full schema with SQL |
| `02_API_REFERENCE.md` | All backend routes, request/response shapes |
| `03_FRONTEND_PAGES.md` | Every page, its fields, actions, and UI behavior |
| `04_BUSINESS_LOGIC.md` | Rules, calculations, validations |
| `05_IMPLEMENTATION_PLAN.md` | Phase-by-phase build order with agent prompts |
| `06_AGENT_PROMPTS.md` | All prompts to feed the agent, phase by phase |

289
01_DATABASE_SCHEMA.md Normal file
View File

@ -0,0 +1,289 @@
# Database Schema — Swish Auto Care GMS
## MySQL 8.x — Full Schema Reference
---
## Setup Instructions
```sql
CREATE DATABASE IF NOT EXISTS swish_gms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE swish_gms;
```
---
## Table 1 — `admin_users`
Stores the single admin login for the demo.
```sql
CREATE TABLE admin_users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
full_name VARCHAR(150) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Seed: password is 'swish@2024' (bcrypt hashed at runtime)
INSERT INTO admin_users (username, full_name, password_hash)
VALUES ('admin', 'Swish Admin', '$2b$10$PLACEHOLDER_BCRYPT_HASH');
-- NOTE: Generate real hash in seed script using bcrypt.hash('swish@2024', 10)
```
---
## Table 2 — `staff`
All staff members of Swish Auto Care.
```sql
CREATE TABLE staff (
id INT AUTO_INCREMENT PRIMARY KEY,
staff_code VARCHAR(20) UNIQUE NOT NULL, -- SAC-STF-001
full_name VARCHAR(150) NOT NULL,
role VARCHAR(100), -- Detailer, Washer, Supervisor
mobile_no VARCHAR(15),
date_of_joining DATE,
monthly_salary DECIMAL(10,2) DEFAULT 0.00,
is_active TINYINT(1) DEFAULT 1, -- 1=Active, 0=Inactive
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
**Auto-code generation logic (in backend):**
```js
// Get last staff code, increment
SELECT staff_code FROM staff ORDER BY id DESC LIMIT 1;
// Parse number, increment, format as SAC-STF-00N
```
---
## Table 3 — `job_cards`
Core operational table. Every vehicle service creates a row here.
```sql
CREATE TABLE job_cards (
id INT AUTO_INCREMENT PRIMARY KEY,
job_no VARCHAR(30) UNIQUE NOT NULL, -- SAC-YYYYMMDD-001
job_type ENUM('full','wash') NOT NULL, -- full=Full Service, wash=Quick Wash
reg_no VARCHAR(20) NOT NULL,
car_name VARCHAR(100) NOT NULL,
owner_name VARCHAR(150) NOT NULL,
mobile_no VARCHAR(15),
service_type VARCHAR(100),
assigned_staff_id INT,
estimated_amount DECIMAL(10,2),
final_amount DECIMAL(10,2),
payment_mode ENUM('cash','upi','card'),
status ENUM('active','done') DEFAULT 'active',
notes TEXT,
job_date DATETIME DEFAULT CURRENT_TIMESTAMP,
closed_at DATETIME NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (assigned_staff_id) REFERENCES staff(id) ON DELETE SET NULL
);
CREATE INDEX idx_job_cards_date ON job_cards(job_date);
CREATE INDEX idx_job_cards_status ON job_cards(status);
CREATE INDEX idx_job_cards_reg ON job_cards(reg_no);
```
**Job number generation logic (backend):**
```js
// Get today's date as YYYYMMDD
// Count jobs created today, add 1
// Format: SAC-20260324-001
const today = new Date().toISOString().slice(0,10).replace(/-/g,'');
const count = await countTodayJobs(); // SELECT COUNT(*) WHERE DATE(job_date) = CURDATE()
const seq = String(count + 1).padStart(3, '0');
const job_no = `SAC-${today}-${seq}`;
```
---
## Table 4 — `attendance`
Daily attendance records for each staff member.
```sql
CREATE TABLE attendance (
id INT AUTO_INCREMENT PRIMARY KEY,
staff_id INT NOT NULL,
attendance_date DATE NOT NULL,
status ENUM('present','absent','half') NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_staff_date (staff_id, attendance_date),
FOREIGN KEY (staff_id) REFERENCES staff(id) ON DELETE CASCADE
);
CREATE INDEX idx_attendance_date ON attendance(attendance_date);
```
**Note:** The UNIQUE KEY on (staff_id, attendance_date) prevents duplicate entries. Use INSERT ... ON DUPLICATE KEY UPDATE for upsert behavior.
---
## Table 5 — `staff_payments`
All salary and payment records for staff.
```sql
CREATE TABLE staff_payments (
id INT AUTO_INCREMENT PRIMARY KEY,
staff_id INT NOT NULL,
payment_date DATE NOT NULL,
payment_month VARCHAR(10) NOT NULL, -- e.g. 'March 2026'
days_present INT DEFAULT 0,
calculated_salary DECIMAL(10,2) DEFAULT 0.00,
paid_amount DECIMAL(10,2) NOT NULL, -- Admin can override
payment_type ENUM('salary','advance','bonus','deduction') NOT NULL,
payment_mode ENUM('cash','bank','upi') NOT NULL,
notes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (staff_id) REFERENCES staff(id) ON DELETE RESTRICT
);
CREATE INDEX idx_payments_staff ON staff_payments(staff_id);
CREATE INDEX idx_payments_date ON staff_payments(payment_date);
```
---
## Table 6 — `parts`
Parts and product inventory master.
```sql
CREATE TABLE parts (
id INT AUTO_INCREMENT PRIMARY KEY,
part_code VARCHAR(20) UNIQUE NOT NULL, -- SAC-PRT-001
name VARCHAR(200) NOT NULL,
category VARCHAR(100), -- Chemicals, Films, Tools, Consumables
unit VARCHAR(30), -- ml, sheet, piece, bottle
purchase_price DECIMAL(10,2) DEFAULT 0.00,
selling_price DECIMAL(10,2) DEFAULT 0.00,
stock_qty INT DEFAULT 0,
low_stock_alert INT DEFAULT 5,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
---
## Table 7 — `parts_sales`
Each parts sale transaction. Stock auto-decrements on insert.
```sql
CREATE TABLE parts_sales (
id INT AUTO_INCREMENT PRIMARY KEY,
part_id INT NOT NULL,
job_card_id INT NULL, -- Optional link to job card
quantity INT NOT NULL,
unit_price DECIMAL(10,2) NOT NULL, -- Selling price at time of sale (snapshot)
total_amount DECIMAL(10,2) NOT NULL, -- quantity × unit_price
customer_name VARCHAR(150),
payment_mode ENUM('cash','upi','card') NOT NULL,
sale_date DATETIME DEFAULT CURRENT_TIMESTAMP,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (part_id) REFERENCES parts(id) ON DELETE RESTRICT,
FOREIGN KEY (job_card_id) REFERENCES job_cards(id) ON DELETE SET NULL
);
CREATE INDEX idx_parts_sales_date ON parts_sales(sale_date);
```
**Stock deduction trigger (handle in backend, not DB trigger):**
```js
// On parts_sales INSERT:
await db.query(
'UPDATE parts SET stock_qty = stock_qty - ? WHERE id = ? AND stock_qty >= ?',
[quantity, part_id, quantity]
);
// Check affected rows — if 0, insufficient stock, rollback
```
---
## Table 8 — `cash_ledger`
Manual cash in/out entries by admin (miscellaneous transactions).
```sql
CREATE TABLE cash_ledger (
id INT AUTO_INCREMENT PRIMARY KEY,
entry_type ENUM('in','out') NOT NULL,
amount DECIMAL(10,2) NOT NULL,
category VARCHAR(100), -- Utilities, Supplies, Advance, Refund, Other
description VARCHAR(500) NOT NULL, -- Mandatory reason
entry_date DATETIME DEFAULT CURRENT_TIMESTAMP,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE INDEX idx_cash_ledger_date ON cash_ledger(entry_date);
```
---
## Complete Schema File (run this to initialize)
Save as `backend/src/config/schema.sql` and run once:
```sql
-- Full initialization script
CREATE DATABASE IF NOT EXISTS swish_gms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE swish_gms;
-- [all CREATE TABLE statements above in order]
-- admin_users → staff → job_cards → attendance → staff_payments → parts → parts_sales → cash_ledger
```
---
## Key Relationships
```
admin_users (standalone — auth only)
staff
├── job_cards.assigned_staff_id → staff.id
├── attendance.staff_id → staff.id
└── staff_payments.staff_id → staff.id
parts
└── parts_sales.part_id → parts.id
job_cards
└── parts_sales.job_card_id → job_cards.id (optional)
```
---
## Seed Data (for demo)
```sql
-- Staff seed
INSERT INTO staff (staff_code, full_name, role, monthly_salary) VALUES
('SAC-STF-001', 'Rajan Mehta', 'Senior Detailer', 18000),
('SAC-STF-002', 'Suresh Kumar', 'Washer', 12000),
('SAC-STF-003', 'Vikram Patel', 'PPF Technician', 22000),
('SAC-STF-004', 'Arjun Shah', 'Supervisor', 25000);
-- Parts seed
INSERT INTO parts (part_code, name, category, unit, purchase_price, selling_price, stock_qty) VALUES
('SAC-PRT-001', 'Ceramic Coat 9H', 'Chemicals', 'bottle', 1200, 2500, 15),
('SAC-PRT-002', 'PPF Film Roll (1m)', 'Films', 'sheet', 800, 1800, 10),
('SAC-PRT-003', 'Microfiber Cloth Set', 'Consumables', 'piece', 150, 350, 50),
('SAC-PRT-004', 'Car Shampoo (1L)', 'Chemicals', 'bottle', 120, 280, 30),
('SAC-PRT-005', 'Dashboard Polish', 'Chemicals', 'bottle', 90, 220, 25);
```

480
02_API_REFERENCE.md Normal file
View File

@ -0,0 +1,480 @@
# API Reference — Swish Auto Care GMS
## Backend: Node.js + Express.js
> Base URL (local): `http://localhost:5000/api`
> All protected routes require: `Authorization: Bearer <JWT_TOKEN>` header.
---
## Auth Routes — `/api/auth`
### POST `/api/auth/login`
**Public.** Validates credentials, returns JWT.
Request:
```json
{ "username": "admin", "password": "swish@2024" }
```
Response (200):
```json
{ "token": "eyJhbG...", "user": { "id": 1, "username": "admin", "full_name": "Swish Admin" } }
```
Response (401): `{ "error": "Invalid credentials" }`
### GET `/api/auth/me`
**Protected.** Returns logged-in user info from JWT.
Response:
```json
{ "id": 1, "username": "admin", "full_name": "Swish Admin" }
```
---
## Dashboard Routes — `/api/dashboard`
### GET `/api/dashboard/today`
**Protected.** Returns all today's summary widgets.
Response:
```json
{
"active_jobs": 4,
"completed_jobs": 7,
"today_revenue": 28500,
"cash_balance": 14200,
"staff_present": 3,
"date": "24/03/2026"
}
```
---
## Job Card Routes — `/api/job-cards`
### GET `/api/job-cards`
**Protected.** List all job cards with optional filters.
Query params: `?status=active|done&date=YYYY-MM-DD&reg_no=GJ06AB1234&page=1&limit=20`
Response:
```json
{
"data": [
{
"id": 1,
"job_no": "SAC-20260324-001",
"job_type": "full",
"reg_no": "GJ06AB1234",
"car_name": "Swift",
"owner_name": "Rajesh Shah",
"mobile_no": "9876543210",
"service_type": "Full Detailing",
"assigned_staff_name": "Rajan Mehta",
"estimated_amount": 5000,
"final_amount": null,
"payment_mode": null,
"status": "active",
"job_date": "2026-03-24T10:30:00",
"closed_at": null
}
],
"total": 1,
"page": 1
}
```
### POST `/api/job-cards`
**Protected.** Create a new job card.
Request (Full Job):
```json
{
"job_type": "full",
"reg_no": "GJ06AB1234",
"car_name": "Swift",
"owner_name": "Rajesh Shah",
"mobile_no": "9876543210",
"service_type": "Full Detailing",
"assigned_staff_id": 1,
"estimated_amount": 5000,
"notes": "Check AC vent"
}
```
Request (Quick Wash):
```json
{
"job_type": "wash",
"reg_no": "GJ06CD5678",
"car_name": "Creta",
"mobile_no": "",
"service_type": "Basic Wash",
"assigned_staff_id": 2,
"estimated_amount": 300
}
```
Response (201):
```json
{ "id": 1, "job_no": "SAC-20260324-001", "message": "Job card created" }
```
### GET `/api/job-cards/:id`
**Protected.** Get single job card with full details.
### PUT `/api/job-cards/:id`
**Protected.** Update job card fields (only while status = 'active').
Request: Partial fields allowed.
```json
{ "notes": "Updated notes", "estimated_amount": 5500 }
```
### POST `/api/job-cards/:id/close`
**Protected.** Mark job as Done. Sets final_amount, payment_mode, closed_at.
Request:
```json
{
"final_amount": 4800,
"payment_mode": "upi"
}
```
Response: `{ "message": "Job marked as done", "closed_at": "2026-03-24T15:45:00" }`
**Error if already done:** `{ "error": "Job already closed" }`
---
## Staff Routes — `/api/staff`
### GET `/api/staff`
**Protected.** List all staff.
Query: `?is_active=1|0`
Response:
```json
{
"data": [
{
"id": 1,
"staff_code": "SAC-STF-001",
"full_name": "Rajan Mehta",
"role": "Senior Detailer",
"mobile_no": "9898989898",
"date_of_joining": "2024-01-15",
"monthly_salary": 18000,
"is_active": 1
}
]
}
```
### POST `/api/staff`
**Protected.** Add new staff member. Auto-generates staff_code.
Request:
```json
{
"full_name": "Deepak Verma",
"role": "Washer",
"mobile_no": "9712345678",
"date_of_joining": "2026-03-01",
"monthly_salary": 13000
}
```
### PUT `/api/staff/:id`
**Protected.** Update staff details.
### PATCH `/api/staff/:id/toggle`
**Protected.** Toggle is_active status.
---
## Attendance Routes — `/api/attendance`
### GET `/api/attendance`
**Protected.** Get attendance for a date (default: today).
Query: `?date=YYYY-MM-DD`
Response:
```json
{
"date": "2026-03-24",
"records": [
{ "staff_id": 1, "staff_name": "Rajan Mehta", "status": "present" },
{ "staff_id": 2, "staff_name": "Suresh Kumar", "status": "absent" },
{ "staff_id": 3, "staff_name": "Vikram Patel", "status": null }
]
}
```
`null` status means not yet marked for today.
### POST `/api/attendance/bulk`
**Protected.** Save attendance for multiple staff in one call.
Request:
```json
{
"date": "2026-03-24",
"records": [
{ "staff_id": 1, "status": "present" },
{ "staff_id": 2, "status": "absent" },
{ "staff_id": 3, "status": "half" }
]
}
```
Response: `{ "message": "Attendance saved", "count": 3 }`
**Backend uses:** `INSERT INTO attendance ... ON DUPLICATE KEY UPDATE status = VALUES(status)`
### GET `/api/attendance/summary/:staffId`
**Protected.** Monthly attendance summary for one staff.
Query: `?month=3&year=2026`
Response:
```json
{
"staff_id": 1,
"month": "March 2026",
"present": 18,
"absent": 3,
"half": 2,
"records": [...]
}
```
---
## Salary Routes — `/api/salary`
### GET `/api/salary`
**Protected.** List all payments, filterable.
Query: `?staff_id=1&month=March 2026`
### POST `/api/salary`
**Protected.** Log a payment to a staff member.
Request:
```json
{
"staff_id": 1,
"payment_date": "2026-03-24",
"payment_month": "March 2026",
"days_present": 22,
"calculated_salary": 15230.77,
"paid_amount": 15000,
"payment_type": "salary",
"payment_mode": "cash",
"notes": "March salary"
}
```
### GET `/api/salary/calculate/:staffId`
**Protected.** Auto-calculate salary based on attendance.
Query: `?month=3&year=2026`
Response:
```json
{
"staff_id": 1,
"staff_name": "Rajan Mehta",
"monthly_salary": 18000,
"days_present": 22,
"calculated_salary": 15230.77
}
```
Formula: `(18000 / 26) * 22 = 15230.77`
---
## Parts Routes — `/api/parts`
### GET `/api/parts`
**Protected.** List all parts with stock info.
Query: `?category=Chemicals&low_stock=true`
Response:
```json
{
"data": [
{
"id": 1,
"part_code": "SAC-PRT-001",
"name": "Ceramic Coat 9H",
"category": "Chemicals",
"unit": "bottle",
"purchase_price": 1200,
"selling_price": 2500,
"stock_qty": 15,
"low_stock_alert": 5,
"is_low": false
}
]
}
```
### POST `/api/parts`
**Protected.** Add new part. Auto-generates part_code.
### PUT `/api/parts/:id`
**Protected.** Update part details (not stock — stock changes via sales).
### PATCH `/api/parts/:id/stock`
**Protected.** Manual stock adjustment (restock).
Request: `{ "adjustment": 10, "reason": "New stock received" }`
---
## Parts Sales Routes — `/api/parts-sales`
### GET `/api/parts-sales`
**Protected.** List all sales.
Query: `?date=YYYY-MM-DD&job_card_id=5`
### POST `/api/parts-sales`
**Protected.** Record a parts sale. Auto-deducts stock.
Request:
```json
{
"part_id": 1,
"job_card_id": null,
"quantity": 2,
"unit_price": 2500,
"total_amount": 5000,
"customer_name": "Ankit Patel",
"payment_mode": "upi"
}
```
**Backend must:**
1. Verify stock_qty >= quantity. If not: `{ "error": "Insufficient stock" }`
2. Insert into parts_sales
3. `UPDATE parts SET stock_qty = stock_qty - ? WHERE id = ?`
4. Return sale record
---
## Cash Ledger Routes — `/api/cash-ledger`
### GET `/api/cash-ledger`
**Protected.** List entries with optional date filter.
Query: `?from=YYYY-MM-DD&to=YYYY-MM-DD&type=in|out`
### POST `/api/cash-ledger`
**Protected.** Add cash in or out entry.
Request:
```json
{
"entry_type": "out",
"amount": 500,
"category": "Utilities",
"description": "Electricity bill payment"
}
```
---
## Accounts Routes — `/api/accounts`
### GET `/api/accounts/summary`
**Protected.** Consolidated financial summary.
Query: `?from=YYYY-MM-DD&to=YYYY-MM-DD` (default: today)
Response:
```json
{
"period": { "from": "2026-03-24", "to": "2026-03-24" },
"income": {
"job_revenue": 28500,
"parts_revenue": 7500,
"cash_in_other": 2000,
"total": 38000
},
"expense": {
"staff_payments": 15000,
"cash_out_other": 800,
"total": 15800
},
"net_balance": 22200
}
```
### GET `/api/accounts/transactions`
**Protected.** All transactions in a date range (for drill-down).
Returns unified list of all financial movements with source type tag.
---
## Reports Routes — `/api/reports`
### GET `/api/reports/daily`
**Protected.** Get daily report data (JSON, for on-screen preview).
Query: `?date=YYYY-MM-DD` (default: today)
### GET `/api/reports/daily/download`
**Protected.** Download daily report as .xlsx file.
Query: `?date=YYYY-MM-DD`
Response: Binary .xlsx stream with headers:
```
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Disposition: attachment; filename="SwishGMS_DailyReport_24-03-2026.xlsx"
```
---
## Export Routes — `/api/exports`
### GET `/api/exports/job-cards`
**Protected.** Export all job cards as .xlsx.
Query: `?from=YYYY-MM-DD&to=YYYY-MM-DD`
### GET `/api/exports/staff-attendance`
**Protected.** Export staff + attendance as .xlsx.
Query: `?month=3&year=2026`
### GET `/api/exports/parts-inventory`
**Protected.** Export parts inventory as .xlsx.
### GET `/api/exports/accounts`
**Protected.** Export accounts/transactions as .xlsx.
Query: `?from=YYYY-MM-DD&to=YYYY-MM-DD`
---
## Error Response Format (Consistent Across All Routes)
```json
{ "error": "Human-readable error message", "code": "OPTIONAL_ERROR_CODE" }
```
HTTP status codes used:
- `200` OK
- `201` Created
- `400` Bad Request (validation errors)
- `401` Unauthorized (missing/invalid JWT)
- `403` Forbidden
- `404` Not Found
- `409` Conflict (e.g. duplicate attendance)
- `500` Internal Server Error

413
03_FRONTEND_PAGES.md Normal file
View File

@ -0,0 +1,413 @@
# Frontend Pages Specification — Swish Auto Care GMS
## React 18 + TypeScript + Vite + React Router v6 + CSS Modules
---
## Global Layout
The app uses a persistent shell layout after login:
```
┌─────────────────────────────────────────────────────────┐
│ TOP BAR: Logo | "Swish Auto Care GMS" | Date | Admin ▼ │
├──────────┬──────────────────────────────────────────────┤
│ │ │
│ SIDEBAR │ PAGE CONTENT │
│ (Left) │ │
│ │ │
└──────────┴──────────────────────────────────────────────┘
```
**Sidebar Nav Items (in order):**
1. 🏠 Dashboard
2. 🔧 Job Cards
3. 📋 Status Board
4. 👤 Staff
5. 📅 Attendance
6. 💰 Salary & Payments
7. 🔩 Parts & Inventory
8. 🛒 Parts Sales
9. 💵 Cash Ledger
10. 📊 Accounts
11. 📈 Daily Report
12. 📤 Excel Exports
**TopBar shows:** Current date in DD/MM/YYYY format | "Admin" label | Logout button
---
## Page 1 — Login (`/login`)
Full-screen centered card. No sidebar/topbar.
**Fields:**
- Username (text input)
- Password (password input)
- "Login" button
**Behavior:**
- On submit: POST /api/auth/login
- On success: Store JWT in localStorage, store user in AuthContext, redirect to /dashboard
- On fail: Show "Invalid username or password" error below form
- Auto-redirect to /dashboard if already logged in
**Design:** Clean card, Swish logo placeholder at top, business colors.
---
## Page 2 — Dashboard (`/dashboard`)
Today's overview. Data from GET /api/dashboard/today.
**6 Widget Cards:**
```
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Active Jobs │ │ Completed Jobs │ │ Today's Revenue │
│ 4 🔧 │ │ 7 ✅ │ │ ₹28,500 💰 │
└──────────────────┘ └──────────────────┘ └──────────────────┘
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Cash Balance │ │ Staff Present │ │ Quick Actions │
│ ₹14,200 │ │ 3 / 4 👤 │ │ + New Job Card │
│ │ │ │ │ Mark Attendance │
└──────────────────┘ └──────────────────┘ └──────────────────┘
```
**Quick action buttons:** "New Job Card" → /job-cards/new | "Mark Attendance" → /attendance | "Daily Report" → /daily-report
**Data refresh:** On page load only (no auto-refresh needed for demo).
---
## Page 3 — Job Cards (`/job-cards`)
List of all job cards with search, filter, and create button.
**Top bar:**
- Search by Reg No. (text input, real-time filter)
- Filter by Status (All / Active / Done) — dropdown
- Filter by Date (date picker, default today)
- "+ New Job Card" button (primary, top right)
**Table columns:**
| Job No | Type | Reg No | Car | Owner | Service | Staff | Amount | Status | Date | Actions |
|--------|------|--------|-----|-------|---------|-------|--------|--------|------|---------|
- Type badge: "Full" (blue) / "Wash" (teal)
- Status badge: "Active" (amber) / "Done" (green)
- Actions: View 👁 | Edit ✏ (only if Active) | Close ✓ (only if Active)
**"Close Job" action:** Opens a modal:
- Final Amount (₹) — required
- Payment Mode — Cash / UPI / Card dropdown
- "Mark as Done" button
---
## Page 4 — New Job Card (`/job-cards/new`)
**Toggle at top:** "Full Service Job" | "Quick Wash Job" — determines which form shows.
**Full Job Form:**
- Registration No. * (text)
- Car Model / Name * (text)
- Owner Name * (text)
- Mobile No. * (text, 10 digits)
- Service Type * (dropdown: Wash, Polish, PPF, Detailing, Interior Cleaning, Ceramic Coating, Underbody Coating, Other)
- Assigned Staff * (dropdown — fetched from active staff list)
- Estimated Amount ₹ (number)
- Notes / Remarks (textarea)
**Quick Wash Form (simplified):**
- Registration No. * (text)
- Car Name * (text)
- Mobile No. (optional)
- Wash Type * (dropdown: Basic Wash, Premium Wash, Interior Clean)
- Assigned Staff * (dropdown)
- Amount ₹ * (number)
**Buttons:** "Create Job Card" (primary) | "Cancel" (secondary)
**On success:** Show toast "Job card SAC-YYYYMMDD-001 created" | Redirect to /job-cards
---
## Page 5 — Status Board (`/status-board`)
Two-column Kanban-style view:
```
┌─────── ACTIVE (4) ──────────┐ ┌─────── DONE (7) ────────────┐
│ SAC-20260324-001 │ │ SAC-20260324-003 │
│ GJ06AB1234 — Swift │ │ GJ05XY9900 — Fortuner │
│ Full Detailing | Rajan M. │ │ Quick Wash | Suresh K. │
│ Est. ₹5,000 | 10:30 AM │ │ ₹350 | 09:15 AM ✅ │
│ [Mark Done] │ │ │
└─────────────────────────────┘ └─────────────────────────────┘
```
Each active card has a "Mark Done" button that opens the Close Job modal (same as in Job Cards list).
---
## Page 6 — Staff (`/staff`)
**Top bar:** "+ Add Staff" button | Search by name
**Table:**
| Staff Code | Name | Role | Mobile | Joining | Salary | Status | Actions |
|-----------|------|------|--------|---------|--------|--------|---------|
- Status: "Active" (green toggle) / "Inactive" (gray toggle) — click to toggle
- Actions: Edit ✏
**Add/Edit Staff Modal:**
- Full Name *
- Role / Designation *
- Mobile No.
- Date of Joining (date picker)
- Monthly Salary ₹ *
- Active toggle
---
## Page 7 — Attendance (`/attendance`)
**Date selector at top** (date picker, default today).
**Attendance grid (bulk mark):**
```
┌─────────────────────────────────────────────────────────┐
│ Date: 24/03/2026 [Save Attendance] │
├──────────────────┬─────────────────────────────────────┤
│ Rajan Mehta │ ● Present ○ Absent ○ Half Day │
│ Suresh Kumar │ ○ Present ● Absent ○ Half Day │
│ Vikram Patel │ ○ Present ○ Absent ● Half Day │
│ Arjun Shah │ ○ Present ○ Absent ○ Half Day │
└──────────────────┴─────────────────────────────────────┘
```
All staff listed. Radio button per row. "Save Attendance" calls POST /api/attendance/bulk.
**Below grid:** "View Monthly Summary" → Modal or accordion per staff showing month grid.
---
## Page 8 — Salary & Payments (`/salary`)
**Two sections:**
**Section A — New Payment:**
- Select Staff * (dropdown)
- Payment Month * (month/year picker or text e.g. "March 2026")
- [Auto-calculate button] → calls GET /api/salary/calculate/:id → fills Days Present & Calculated Salary fields
- Days Present (auto-filled, editable)
- Calculated Salary ₹ (auto-filled, read-only display)
- Payment Amount ₹ * (editable — admin override)
- Payment Type * (Full Salary / Advance / Bonus / Deduction)
- Payment Mode * (Cash / Bank / UPI)
- Notes (textarea)
- "Record Payment" button
**Section B — Payment History Table:**
- Filter by Staff and Month
- Columns: Date | Staff | Month | Type | Mode | Amount | Notes
---
## Page 9 — Parts & Inventory (`/parts`)
**Top bar:** "+ Add Part" | Search | Filter by Category | Low Stock toggle
**Table:**
| Part Code | Name | Category | Unit | Buy Price | Sell Price | Stock | Low Stock? | Actions |
|----------|------|----------|------|-----------|------------|-------|------------|---------|
- Low Stock?: Red badge if stock_qty ≤ low_stock_alert
- Actions: Edit ✏ | Restock + (opens restock modal)
**Add/Edit Part Modal:** All fields from parts table.
**Restock Modal:** Adjustment quantity input + reason.
---
## Page 10 — Parts Sales (`/parts-sales`)
**Top bar:** "+ Record Sale" button | Date filter
**Sales history table:**
| Date | Part | Qty | Unit Price | Total | Customer | Payment | Job Card |
|------|------|-----|-----------|-------|----------|---------|---------|
**New Sale Modal:**
- Select Part * (searchable dropdown showing part name + stock)
- Link to Job Card? (optional — searchable dropdown of active + recent job cards)
- Quantity * (number — validated against stock)
- Unit Price ₹ (auto-filled from part.selling_price, editable)
- Total Amount (auto-calculated, display only)
- Customer Name (optional text)
- Payment Mode * (Cash / UPI / Card)
- "Record Sale" button
**Validation:** If quantity > stock_qty, show error "Insufficient stock (X available)".
---
## Page 11 — Cash Ledger (`/cash-ledger`)
**Top bar:** "+ Cash In" | "+ Cash Out" | Date range filter
**Ledger table:**
| Date | Type | Category | Description | Amount |
|------|------|----------|-------------|--------|
- Type badge: "IN" (green) / "OUT" (red)
- Running balance column (optional — nice to have)
**New Entry Modal (shared for In and Out, pre-fills type):**
- Entry Type (pre-selected Cash In or Cash Out)
- Amount ₹ *
- Category * (dropdown: Utilities, Supplies, Advance, Refund, Other)
- Description * (mandatory free text)
- Date & Time (auto-filled, editable)
---
## Page 12 — Accounts (`/accounts`)
**Date Range filter at top:** Today | This Week | This Month | Custom Range
**Summary cards:**
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Total Income │ │ Total Expense│ │ Net Balance │
│ ₹38,000 │ │ ₹15,800 │ │ ₹22,200 │
└──────────────┘ └──────────────┘ └──────────────┘
```
**Breakdown table (Income):**
| Source | Amount |
| Job Revenue | ₹28,500 |
| Parts Sales | ₹7,500 |
| Cash In (Other) | ₹2,000 |
**Breakdown table (Expenses):**
| Source | Amount |
| Staff Payments | ₹15,000 |
| Cash Out (Other) | ₹800 |
**All Transactions tab:** Unified chronological list, clickable to see source.
---
## Page 13 — Daily Report (`/daily-report`)
**Date picker** (default today).
**On-screen preview sections:**
1. Header: Business name, date, summary totals
2. Completed Jobs (table)
3. Parts Sales (table)
4. Cash In entries (table)
5. Cash Out entries (table)
6. Staff Payments (table)
7. Day Totals (summary row)
**"Download Excel" button** → calls GET /api/reports/daily/download?date=YYYY-MM-DD → browser saves .xlsx file.
---
## Page 14 — Excel Exports (`/exports`)
4 export cards, each with description and download button:
```
┌───────────────────────────────────────┐
│ 📥 Job Cards Database │
│ Export all job cards with date filter │
│ [From Date] [To Date] [Download xlsx] │
└───────────────────────────────────────┘
┌───────────────────────────────────────┐
│ 📥 Staff & Attendance │
│ Export staff list + monthly attendance│
│ [Month] [Year] [Download xlsx] │
└───────────────────────────────────────┘
┌───────────────────────────────────────┐
│ 📥 Parts Inventory │
│ Full parts catalog with stock levels │
│ [Download xlsx] │
└───────────────────────────────────────┘
┌───────────────────────────────────────┐
│ 📥 Accounts Ledger │
│ All financial transactions │
│ [From Date] [To Date] [Download xlsx] │
└───────────────────────────────────────┘
```
---
## Shared Components
### `<Badge status="active|done|in|out|full|wash" />`
Colored pill badge for statuses.
### `<Modal title onClose>children</Modal>`
Overlay modal with close button.
### `<DataTable columns data loading onAction />`
Reusable table with loading skeleton.
### `<ConfirmDialog message onConfirm onCancel />`
Simple yes/no confirmation popup.
### Toast Notifications
Use a simple toast system: success (green), error (red), info (blue). Auto-dismiss in 3s.
---
## AuthContext & Protected Routes
```tsx
// context/AuthContext.tsx
interface AuthContextType {
user: { id: number; username: string; full_name: string } | null;
token: string | null;
login: (token: string, user: object) => void;
logout: () => void;
isAuthenticated: boolean;
}
// Protected route wrapper
<ProtectedRoute> wraps all pages except /login
// Redirects to /login if !isAuthenticated
```
---
## Axios Instance
```ts
// api/axios.ts
import axios from 'axios';
const api = axios.create({ baseURL: 'http://localhost:5000/api' });
// Request interceptor — attach JWT
api.interceptors.request.use(config => {
const token = localStorage.getItem('gms_token');
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// Response interceptor — handle 401 (auto logout)
api.interceptors.response.use(
r => r,
err => {
if (err.response?.status === 401) {
localStorage.removeItem('gms_token');
window.location.href = '/login';
}
return Promise.reject(err);
}
);
export default api;
```

337
04_BUSINESS_LOGIC.md Normal file
View File

@ -0,0 +1,337 @@
# Business Logic, Rules & Validations — Swish Auto Care GMS
---
## 1. Job Card Rules
### Job Number Generation
```js
// Format: SAC-YYYYMMDD-SEQ
// SEQ = count of jobs created today + 1, zero-padded to 3 digits
// Example: SAC-20260324-001, SAC-20260324-002
async function generateJobNo(db) {
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
const dateStr = today.replace(/-/g, ''); // YYYYMMDD
const [rows] = await db.query(
'SELECT COUNT(*) as cnt FROM job_cards WHERE DATE(job_date) = ?',
[today]
);
const seq = String(rows[0].cnt + 1).padStart(3, '0');
return `SAC-${dateStr}-${seq}`;
}
```
### Status Transitions
- New job → always starts as `'active'`
- `active``done`: triggered by POST /job-cards/:id/close
- **No reverse transition.** `done` cannot go back to `active` in demo.
- Cannot edit `final_amount`, `payment_mode`, `closed_at` once set.
### Field Requirements by Job Type
| Field | Full Job | Quick Wash |
|-------|----------|-----------|
| reg_no | Required | Required |
| car_name | Required | Required |
| owner_name | Required | Optional |
| mobile_no | Required | Optional |
| service_type | Required | Required |
| assigned_staff_id | Required | Required |
| estimated_amount | Optional | Optional |
| notes | Optional | Not shown |
### Closing a Job Card
```js
// Validate before closing:
if (!final_amount || final_amount <= 0) throw 'Final amount required';
if (!payment_mode) throw 'Payment mode required';
if (job.status === 'done') throw 'Job already closed';
// On close:
UPDATE job_cards SET
status = 'done',
final_amount = ?,
payment_mode = ?,
closed_at = NOW()
WHERE id = ? AND status = 'active'
```
---
## 2. Staff Code Generation
```js
async function generateStaffCode(db) {
const [rows] = await db.query(
'SELECT staff_code FROM staff ORDER BY id DESC LIMIT 1'
);
if (rows.length === 0) return 'SAC-STF-001';
const last = rows[0].staff_code; // e.g. SAC-STF-004
const num = parseInt(last.split('-')[2]) + 1;
return `SAC-STF-${String(num).padStart(3, '0')}`;
}
```
---
## 3. Attendance Rules
### Bulk Save (Upsert)
```sql
INSERT INTO attendance (staff_id, attendance_date, status)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE status = VALUES(status), updated_at = NOW();
```
### Monthly Summary Calculation
```js
function calculateAttendanceSummary(records) {
const present = records.filter(r => r.status === 'present').length;
const absent = records.filter(r => r.status === 'absent').length;
const half = records.filter(r => r.status === 'half').length;
// Half day counts as 0.5 for salary purposes
const effectiveDays = present + (half * 0.5);
return { present, absent, half, effectiveDays };
}
```
---
## 4. Salary Calculation
### Formula
```
Calculated Salary = (Monthly Salary / 26) × Effective Days Present
Where:
Effective Days = present_days + (half_days × 0.5)
26 = standard working days per month denominator (industry standard)
```
```js
function calculateSalary(monthlySalary, daysPresent, halfDays = 0) {
const effectiveDays = daysPresent + (halfDays * 0.5);
const dailyRate = monthlySalary / 26;
return Math.round(dailyRate * effectiveDays * 100) / 100; // 2 decimal places
}
```
### Admin Override
- `calculated_salary` is always stored (what the formula says)
- `paid_amount` is what admin actually pays (can be different)
- Both fields stored in staff_payments — never lose the audit trail
### Payment Types
- `salary` — regular monthly salary
- `advance` — advance against next month's salary
- `bonus` — extra payment (festival, performance)
- `deduction` — penalty or loan recovery (stored as negative in accounts)
For `deduction` type: store `paid_amount` as positive number, flag as deduction type. In accounts, treat as income-reducing or expense depending on business interpretation. For simplicity in demo: deduction payments are NOT shown in accounts expense — they are simply logged records.
---
## 5. Parts & Stock Rules
### Part Code Generation
```js
async function generatePartCode(db) {
const [rows] = await db.query(
'SELECT part_code FROM parts ORDER BY id DESC LIMIT 1'
);
if (rows.length === 0) return 'SAC-PRT-001';
const last = rows[0].part_code;
const num = parseInt(last.split('-')[2]) + 1;
return `SAC-PRT-${String(num).padStart(3, '0')}`;
}
```
### Stock Deduction (Atomic Operation)
```js
// Must check and deduct atomically to prevent overselling
const [result] = await db.query(
'UPDATE parts SET stock_qty = stock_qty - ? WHERE id = ? AND stock_qty >= ?',
[quantity, part_id, quantity]
);
if (result.affectedRows === 0) {
throw new Error('Insufficient stock');
}
// Only insert sale record after successful stock deduction
```
### Low Stock Alert
```js
// is_low flag returned in GET /api/parts:
const is_low = part.stock_qty <= part.low_stock_alert;
```
### Price Snapshot
- When a sale is recorded, store `unit_price` from the sale moment (not always current selling_price)
- This allows price changes without affecting historical records
---
## 6. Cash Ledger Rules
- `description` field is **mandatory** — reject if empty/whitespace
- `amount` must be > 0
- `entry_type` must be 'in' or 'out'
- `entry_date` defaults to NOW() but admin can edit (for backdating if they forgot)
---
## 7. Accounts Module — How It Aggregates
The accounts view pulls from 4 sources:
```sql
-- INCOME
SELECT
SUM(CASE WHEN status='done' THEN final_amount ELSE 0 END) as job_revenue
FROM job_cards
WHERE DATE(closed_at) BETWEEN ? AND ?;
SELECT SUM(total_amount) as parts_revenue
FROM parts_sales
WHERE DATE(sale_date) BETWEEN ? AND ?;
SELECT SUM(amount) as cash_in_other
FROM cash_ledger
WHERE entry_type = 'in' AND DATE(entry_date) BETWEEN ? AND ?;
-- EXPENSE
SELECT SUM(paid_amount) as staff_payments
FROM staff_payments
WHERE payment_date BETWEEN ? AND ? AND payment_type != 'deduction';
SELECT SUM(amount) as cash_out_other
FROM cash_ledger
WHERE entry_type = 'out' AND DATE(entry_date) BETWEEN ? AND ?;
```
---
## 8. Daily Sales Report — Excel Structure
The report Excel file should have these sheets (or sections within one sheet):
**Sheet 1: Daily Summary**
```
Row 1: SWISH AUTO CARE — DAILY SALES REPORT
Row 2: Date: 24/03/2026
Row 3: (blank)
Row 4: Total Income | ₹38,000
Row 5: Total Expense | ₹15,800
Row 6: Net Balance | ₹22,200
```
**Sheet 2: Job Revenue**
Columns: Job No | Reg No | Car | Service | Staff | Amount | Payment Mode | Time
**Sheet 3: Parts Sales**
Columns: Part | Qty | Unit Price | Total | Customer | Payment | Time
**Sheet 4: Cash In**
Columns: Category | Description | Amount | Time
**Sheet 5: Cash Out**
Columns: Category | Description | Amount | Time
**Sheet 6: Staff Payments**
Columns: Staff | Type | Mode | Amount | Notes
---
## 9. Excel Export — SheetJS Pattern
```js
const XLSX = require('xlsx');
function createExcelResponse(res, filename, sheetsData) {
const wb = XLSX.utils.book_new();
sheetsData.forEach(({ sheetName, data }) => {
const ws = XLSX.utils.json_to_sheet(data);
XLSX.utils.book_append_sheet(wb, ws, sheetName);
});
const buffer = XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' });
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
res.send(buffer);
}
```
---
## 10. JWT Auth Pattern
```js
// backend/src/middleware/auth.js
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch {
return res.status(401).json({ error: 'Invalid or expired token' });
}
};
// On login, sign:
const token = jwt.sign(
{ id: user.id, username: user.username },
process.env.JWT_SECRET,
{ expiresIn: '8h' }
);
```
---
## 11. Input Validations (Frontend + Backend)
| Field | Rule |
|-------|------|
| Mobile No. | 10 digits, numeric only |
| Amount fields | Positive number, max 2 decimal places |
| Quantity | Positive integer |
| Date fields | Valid date, not future for attendance |
| Reg No | Uppercase, alphanumeric, trim whitespace |
| Description (cash) | Non-empty after trim, min 3 chars |
| Payment mode | Must be one of: cash, upi, card |
| Username/password | Non-empty, min 3 chars |
---
## 12. Date & Currency Formatting (Frontend)
```ts
// Currency: Always show ₹ with comma formatting
function formatCurrency(amount: number): string {
return `₹${amount.toLocaleString('en-IN', { minimumFractionDigits: 0 })}`;
}
// ₹28,500 (not ₹28500)
// Date: DD/MM/YYYY
function formatDate(dateStr: string): string {
const d = new Date(dateStr);
return d.toLocaleDateString('en-IN', { day:'2-digit', month:'2-digit', year:'numeric' });
}
// 24/03/2026
// DateTime: DD/MM/YYYY HH:MM
function formatDateTime(dateStr: string): string {
const d = new Date(dateStr);
return d.toLocaleDateString('en-IN', { day:'2-digit', month:'2-digit', year:'numeric', hour:'2-digit', minute:'2-digit' });
}
```

289
05_IMPLEMENTATION_PLAN.md Normal file
View File

@ -0,0 +1,289 @@
# Implementation Plan — Swish Auto Care GMS
## For Claude Opus Agent — Build Order & Phase Structure
---
## Overview
Build this system in **6 phases**, one at a time. Each phase is self-contained and delivers working, testable functionality before moving to the next. Do NOT start the next phase until the current phase is fully working.
---
## Pre-Build Checklist (Do First)
Before writing any code, verify the environment:
```bash
# Required installed:
node --version # 18+ required
mysql --version # 8.x
npm --version # 9+
# Create project structure:
mkdir swish-gms && cd swish-gms
mkdir backend frontend
```
---
## Phase 1 — Foundation (Build First)
**Goal:** Database up, backend server running, admin can login, frontend shell renders.
### Backend Tasks:
1. `cd backend && npm init -y`
2. Install: `npm install express mysql2 jsonwebtoken bcryptjs dotenv cors`
3. Install dev: `npm install -D nodemon`
4. Create `.env`:
```
PORT=5000
DB_HOST=localhost
DB_USER=root
DB_PASS=your_mysql_password
DB_NAME=swish_gms
JWT_SECRET=swishgms_secret_key_2026
```
5. Create `src/config/db.js` — MySQL connection pool using `mysql2/promise`
6. Run the full schema SQL from `01_DATABASE_SCHEMA.md` in MySQL
7. Create `src/middleware/auth.js` — JWT verify middleware
8. Create `src/routes/auth.js` — POST /login, GET /me
9. Create `src/server.js` — Express app with CORS, JSON body parser, routes
10. Create seed script — hash password, insert admin user, insert sample staff and parts
11. Test: `POST http://localhost:5000/api/auth/login` with Postman/curl
### Frontend Tasks:
1. `cd frontend && npm create vite@latest . -- --template react-ts`
2. Install: `npm install react-router-dom axios`
3. Create `src/api/axios.ts` — Axios instance with base URL and JWT interceptors
4. Create `src/context/AuthContext.tsx` — Auth state, login/logout functions
5. Create `src/pages/Login.tsx` — Login form
6. Create `src/components/Layout/` — Sidebar, TopBar, Layout wrapper
7. Create `src/App.tsx` — Router setup with ProtectedRoute wrapper
8. Create placeholder page components for all 14 routes (just `<div>Page Name</div>`)
9. Test: Open http://localhost:5173, login page shows, login works, redirects to dashboard placeholder
### Phase 1 Done When:
- [ ] MySQL schema created, all 8 tables exist
- [ ] Admin can login via API and get JWT
- [ ] Frontend shows login page at /
- [ ] After login, sidebar shows with all nav items
- [ ] Logout works and returns to login page
---
## Phase 2 — Job Cards & Status Board
**Goal:** Admin can create, view, and close job cards. Status board works.
### Backend Tasks:
1. Create `src/routes/jobCards.js` with all routes from `02_API_REFERENCE.md`
2. Implement job number generation (SAC-YYYYMMDD-001)
3. Implement GET with filters (status, date, reg_no, pagination)
4. Implement POST (create full and quick wash)
5. Implement PUT (edit while active)
6. Implement POST /:id/close (mark done with validation)
7. Create `src/routes/dashboard.js` — GET /today (needs job data)
### Frontend Tasks:
1. Build `src/pages/JobCards.tsx` — table with search, filter, actions
2. Build `src/pages/JobCardNew.tsx` — dual-form (Full / Quick Wash toggle)
3. Build close job modal (inline in JobCards.tsx)
4. Build `src/pages/StatusBoard.tsx` — two-column Active/Done view
5. Build `src/pages/Dashboard.tsx` — 6 widget cards, quick actions
6. Build shared `<Badge>` component
7. Build shared `<Modal>` component
### Phase 2 Done When:
- [ ] AC-02: Can create Full and Quick Wash job cards
- [ ] AC-03: Jobs show as Active, can be marked Done with amount + payment
- [ ] AC-01 (partial): Dashboard loads with active/done job counts and revenue
- [ ] Status board shows two columns
---
## Phase 3 — Staff, Attendance & Salary
**Goal:** Staff management, daily attendance marking, and salary logging all work.
### Backend Tasks:
1. Create `src/routes/staff.js` — CRUD, toggle active
2. Create `src/routes/attendance.js` — GET by date, bulk POST, monthly summary
3. Create `src/routes/salary.js` — list, create payment, GET calculate endpoint
4. Update `src/routes/dashboard.js` — add staff_present count
5. Add staff count to dashboard API
### Frontend Tasks:
1. Build `src/pages/Staff.tsx` — table, add/edit modal, toggle active
2. Build `src/pages/Attendance.tsx` — bulk attendance marking grid with date picker
3. Build `src/pages/Salary.tsx` — payment form with auto-calculate + history table
### Phase 3 Done When:
- [ ] AC-04: Admin can mark daily attendance for all staff
- [ ] AC-05: Admin can log salary/advance, view history
- [ ] Dashboard shows staff_present count correctly
- [ ] New job card dropdown shows active staff list
---
## Phase 4 — Parts, Sales & Cash Ledger
**Goal:** Inventory, POS-style parts sales, and manual cash entries all work.
### Backend Tasks:
1. Create `src/routes/parts.js` — CRUD, stock adjustment
2. Create `src/routes/partsSales.js` — list, create sale (with stock deduction)
3. Create `src/routes/cashLedger.js` — list, create entry
4. Add parts_revenue to dashboard totals
### Frontend Tasks:
1. Build `src/pages/Parts.tsx` — parts table with low stock badge, restock modal
2. Build `src/pages/PartsSales.tsx` — sales table, new sale modal with stock check
3. Build `src/pages/CashLedger.tsx` — ledger table, cash in/out modal
### Phase 4 Done When:
- [ ] AC-06: Admin can record parts sale, stock quantity decreases
- [ ] AC-07: Admin can enter cash in/out with reasons
- [ ] Dashboard today_revenue now includes parts_revenue
---
## Phase 5 — Accounts, Reports & Exports
**Goal:** Accounts consolidation, daily report download, and all 4 Excel exports work.
### Backend Tasks:
1. Create `src/routes/accounts.js` — summary (aggregate query), transactions list
2. Create `src/routes/reports.js` — daily report JSON + Excel download
3. Create `src/routes/exports.js` — 4 separate Excel export endpoints
4. Install SheetJS: `npm install xlsx`
5. Create `src/utils/excel.js` — helper for creating Excel files (see 04_BUSINESS_LOGIC.md)
### Frontend Tasks:
1. Build `src/pages/Accounts.tsx` — summary cards, income/expense breakdown, transactions
2. Build `src/pages/DailyReport.tsx` — date picker, on-screen preview tables, download button
3. Build `src/pages/Exports.tsx` — 4 export cards with date pickers and download buttons
4. Handle file download in frontend (trigger blob download from axios response)
### Excel Download Pattern (Frontend):
```ts
const downloadExcel = async (url: string, filename: string, params?: object) => {
const response = await api.get(url, { params, responseType: 'blob' });
const blob = new Blob([response.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
};
```
### Phase 5 Done When:
- [ ] AC-08: Accounts shows today's income, expenses, balance
- [ ] AC-09: Daily report downloads as formatted Excel
- [ ] AC-10: All 4 export buttons produce correct .xlsx files
---
## Phase 6 — Polish, Testing & Seed Data
**Goal:** Everything looks good, works reliably, has realistic demo data.
### Tasks:
1. Run through all 10 AC acceptance criteria manually
2. Fix any bugs found
3. Create comprehensive seed data:
- 5-10 job cards (mix of active and done, various dates)
- 30 days of attendance for all 4 staff
- 3-4 salary payment records
- 10-15 parts sales
- 15-20 cash ledger entries (mix in/out)
4. UI polish:
- Loading states (skeleton loaders or spinner) on all data tables
- Error states when API calls fail
- Empty states ("No job cards yet" with create button)
- Toast notifications for all create/update actions
- Confirm dialog before marking job done
5. Update Dashboard `today_revenue` to include ALL income sources
6. Verify all date formatting is DD/MM/YYYY
7. Verify all currency shows ₹ with Indian comma formatting
8. Final test: full demo walkthrough
### Phase 6 Done When:
- [ ] All 10 ACs pass
- [ ] Demo data loaded and looks realistic
- [ ] No console errors in browser
- [ ] No server errors in terminal
- [ ] Admin password works: username=admin, password=swish@2024
---
## Dependency Install Summary
### Backend
```bash
cd backend
npm install express mysql2 jsonwebtoken bcryptjs dotenv cors xlsx
npm install -D nodemon
```
### package.json scripts (backend):
```json
{
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js",
"seed": "node src/config/seed.js"
}
}
```
### Frontend
```bash
cd frontend
npm install react-router-dom axios
```
---
## Environment Variables (.env — Backend)
```
PORT=5000
DB_HOST=localhost
DB_USER=root
DB_PASS=your_mysql_root_password
DB_NAME=swish_gms
JWT_SECRET=swishgms_jwt_secret_2026_local
NODE_ENV=development
```
---
## Running the App
```bash
# Terminal 1 — Backend
cd backend && npm run dev
# Terminal 2 — Frontend
cd frontend && npm run dev
# Browser
http://localhost:5173
Login: admin / swish@2024
```
---
## Common Mistakes to Avoid
1. **Don't use `mysql` package** — use `mysql2/promise` for async/await support
2. **Don't forget CORS** — frontend on :5173, backend on :5000 — need CORS middleware
3. **Don't store plain passwords** — always bcrypt.hash before insert
4. **Don't skip the ON DUPLICATE KEY for attendance** — must upsert, not insert
5. **Don't forget stock validation** — check stock before inserting parts sale
6. **Don't use DELETE** — business rule says no deletion, only voiding (not needed in demo but don't implement delete routes)
7. **Excel download needs responseType: 'blob'** in frontend axios call
8. **JWT_SECRET must match** between sign and verify — both use same env var

532
06_AGENT_PROMPTS.md Normal file
View File

@ -0,0 +1,532 @@
# Agent Prompts — Swish Auto Care GMS
## All Prompts for Claude Opus 4 in Claude Code (Antigravity)
> **How to use this file:**
> Copy each prompt exactly as written into Claude Code, one phase at a time.
> Wait for the phase to complete and be verified before moving to the next prompt.
> All context files should be in the same project folder so the agent can reference them.
---
## INITIAL SETUP PROMPT (Run This First — Before Any Phase)
```
You are building the Swish Auto Care Garage Management System (GMS) from scratch.
Before you write any code, read these context files in this exact order:
1. 00_PROJECT_OVERVIEW.md — master context and what you're building
2. 01_DATABASE_SCHEMA.md — all MySQL tables and relationships
3. 02_API_REFERENCE.md — all backend API routes
4. 03_FRONTEND_PAGES.md — all frontend pages and their behavior
5. 04_BUSINESS_LOGIC.md — rules, calculations, and validations
6. 05_IMPLEMENTATION_PLAN.md — the build order and phases
After reading all 6 files, confirm you understand the full system by giving me:
- A one-paragraph summary of what Swish Auto Care GMS does
- The tech stack you will use (exact packages)
- The 10 modules you will build (M1M10)
- Confirmation that you understand the 6-phase build order
Do NOT write any code yet. Just confirm your understanding.
```
---
## PHASE 1 PROMPT — Foundation
```
You are building the Swish Auto Care GMS. Read 00_PROJECT_OVERVIEW.md, 01_DATABASE_SCHEMA.md, 02_API_REFERENCE.md, and 04_BUSINESS_LOGIC.md before starting.
Build Phase 1 — Foundation. Do all of these tasks in order:
BACKEND:
1. Create the folder structure: swish-gms/backend/src/config/, middleware/, routes/
2. Initialize npm in backend/ and install: express mysql2 jsonwebtoken bcryptjs dotenv cors
3. Install dev dependency: nodemon
4. Create backend/.env with:
PORT=5000, DB_HOST=localhost, DB_USER=root, DB_PASS=root, DB_NAME=swish_gms, JWT_SECRET=swishgms_jwt_secret_2026_local
5. Create backend/src/config/db.js — MySQL connection pool using mysql2/promise with promise wrapper
6. Create backend/src/config/schema.sql — complete SQL with all 8 tables (from 01_DATABASE_SCHEMA.md). Include CREATE DATABASE IF NOT EXISTS.
7. Create backend/src/config/seed.js — a runnable Node.js script that:
a. Hashes the password 'swish@2024' using bcryptjs
b. Inserts admin user (username: admin)
c. Inserts 4 sample staff members (from seed data in 01_DATABASE_SCHEMA.md)
d. Inserts 5 sample parts (from seed data in 01_DATABASE_SCHEMA.md)
e. Prints "Seed complete" when done
8. Create backend/src/middleware/auth.js — JWT verify middleware (pattern in 04_BUSINESS_LOGIC.md)
9. Create backend/src/routes/auth.js — POST /login (validate, return JWT + user) and GET /me (protected)
10. Create backend/src/server.js — Express app with CORS (origin: http://localhost:5173), JSON body parser, /api/auth route mounted
11. Add scripts to package.json: "dev": "nodemon src/server.js", "seed": "node src/config/seed.js"
FRONTEND:
12. Create frontend/ using: npm create vite@latest frontend -- --template react-ts
13. Install: react-router-dom axios
14. Create frontend/src/api/axios.ts — Axios instance (baseURL: http://localhost:5000/api) with request interceptor for JWT and response interceptor for 401 auto-logout (pattern in 03_FRONTEND_PAGES.md)
15. Create frontend/src/context/AuthContext.tsx — AuthContext with user, token, login(), logout(), isAuthenticated (store token in localStorage key 'gms_token')
16. Create frontend/src/pages/Login.tsx — full login form page (centered card, username + password fields, submit calls POST /api/auth/login, stores JWT, redirects to /dashboard)
17. Create frontend/src/components/Layout/Sidebar.tsx — sidebar with all 12 nav links listed in 03_FRONTEND_PAGES.md
18. Create frontend/src/components/Layout/TopBar.tsx — shows "Swish Auto Care GMS", current date in DD/MM/YYYY, "Admin" label, Logout button
19. Create frontend/src/components/Layout/Layout.tsx — wrapper that renders Sidebar + TopBar + main content area
20. Create placeholder page files (just return <div>PageName Page</div>) for: Dashboard, JobCards, JobCardNew, StatusBoard, Staff, Attendance, Salary, Parts, PartsSales, CashLedger, Accounts, DailyReport, Exports
21. Create frontend/src/App.tsx — React Router setup with: /login (public), all other routes wrapped in ProtectedRoute (redirects to /login if not authenticated), / redirects to /dashboard
22. Verify frontend/src/main.tsx wraps App in AuthContext provider
TESTING INSTRUCTIONS I WILL RUN:
- Terminal 1: cd backend && npm run dev → should print "Server running on port 5000"
- MySQL: Run schema.sql, then npm run seed → "Seed complete"
- Test login: curl -X POST http://localhost:5000/api/auth/login -H "Content-Type: application/json" -d '{"username":"admin","password":"swish@2024"}' → should return token
- Terminal 2: cd frontend && npm run dev → open http://localhost:5173
- Login page should show → login should work → sidebar should appear
Do not move on to Phase 2 tasks. Only deliver Phase 1.
```
---
## PHASE 2 PROMPT — Job Cards & Status Board
```
Phase 1 is complete and working. Now build Phase 2 — Job Cards & Status Board.
Read 02_API_REFERENCE.md (Job Card Routes section) and 03_FRONTEND_PAGES.md (Pages 2-5) and 04_BUSINESS_LOGIC.md (Section 1) before starting.
BACKEND:
1. Create backend/src/routes/jobCards.js with these routes (all protected by auth middleware):
- GET /api/job-cards — list with filters: status, date (YYYY-MM-DD), reg_no, page, limit. Join with staff table to get assigned_staff_name. Default: today's jobs.
- POST /api/job-cards — create new job card. Auto-generate job_no using the SAC-YYYYMMDD-SEQ format (see 04_BUSINESS_LOGIC.md Section 1). Validate required fields based on job_type.
- GET /api/job-cards/:id — single job card with staff name
- PUT /api/job-cards/:id — update fields only if status = 'active'
- POST /api/job-cards/:id/close — mark done: require final_amount > 0 and payment_mode, set status='done', closed_at=NOW(), reject if already done
2. Create backend/src/routes/dashboard.js (protected):
- GET /api/dashboard/today — return: active_jobs (count where status='active' and DATE(job_date)=today), completed_jobs (count where status='done' and DATE(closed_at)=today), today_revenue (SUM of final_amount where status='done' and DATE(closed_at)=today), cash_balance (set to 0 for now — will add later), staff_present (set to 0 for now), date (formatted DD/MM/YYYY)
3. Mount both routes in server.js
FRONTEND:
4. Create frontend/src/components/shared/Badge.tsx — takes status prop ('active'|'done'|'full'|'wash'|'in'|'out'), returns colored pill span. Colors: active=amber, done=green, full=blue, wash=teal, in=green, out=red.
5. Create frontend/src/components/shared/Modal.tsx — overlay modal with title, close button (X), and children slot. Click outside to close.
6. Build frontend/src/pages/Dashboard.tsx:
- Calls GET /api/dashboard/today on load
- Shows 6 widget cards: Active Jobs, Completed Jobs, Today's Revenue (₹ format), Cash Balance (₹ format), Staff Present, Quick Actions
- Quick actions card has 3 buttons: "New Job Card" (→ /job-cards/new), "Mark Attendance" (→ /attendance), "Daily Report" (→ /daily-report)
- Show loading skeleton while fetching
7. Build frontend/src/pages/JobCards.tsx:
- Calls GET /api/job-cards on load and on filter change
- Search bar for reg_no (debounced 500ms)
- Status dropdown filter (All / Active / Done)
- Date picker (default today)
- Table with columns: Job No, Type (Badge), Reg No, Car, Owner, Service, Staff, Amount (show final if done, estimated if active), Status (Badge), Date, Actions
- Actions: View icon (expand row or simple alert for now), Edit icon (only if active — opens edit modal), Close icon (only if active — opens Close Job modal)
- Close Job modal: Final Amount input (₹), Payment Mode dropdown (Cash/UPI/Card), "Mark as Done" button. On submit: POST /api/job-cards/:id/close, refresh list, show success toast
- "+ New Job Card" button top right → navigate to /job-cards/new
8. Build frontend/src/pages/JobCardNew.tsx:
- Toggle at top: "Full Service Job" | "Quick Wash Job" — switches which form renders
- Full job form fields (all from 03_FRONTEND_PAGES.md Page 4): reg_no*, car_name*, owner_name*, mobile_no*, service_type* (dropdown with options from PRD), assigned_staff_id* (dropdown — fetch from GET /api/staff?is_active=1), estimated_amount, notes
- Quick wash form fields: reg_no*, car_name*, mobile_no (optional), service_type* (Basic Wash/Premium Wash/Interior Clean), assigned_staff_id*, estimated_amount*
- On submit: POST /api/job-cards, show success toast with job_no, navigate to /job-cards
- Cancel button → navigate to /job-cards
9. Build frontend/src/pages/StatusBoard.tsx:
- Two columns side by side: ACTIVE (amber header) | DONE (green header)
- Fetch GET /api/job-cards with no date filter and show all active + today's done
- Each card shows: job_no, reg_no — car_name, service_type | assigned_staff_name, amount, time
- Active cards have "Mark Done" button → same Close Job modal as JobCards.tsx
- Card counts in column headers: "ACTIVE (4)" | "DONE (7)"
10. Create simple toast notification system (can be a simple fixed-bottom-right div with auto-dismiss):
frontend/src/components/shared/Toast.tsx
TESTING I WILL RUN:
- Create a Full Job card, verify job_no is SAC-[today]-001
- Create a Quick Wash job card
- See both in job cards list
- Mark one as done, verify status changes to Done with final_amount
- Dashboard shows updated counts
- Status board shows Active and Done columns
```
---
## PHASE 3 PROMPT — Staff, Attendance & Salary
```
Phase 2 is complete. Build Phase 3 — Staff Management, Attendance, and Salary.
Read 02_API_REFERENCE.md (Staff, Attendance, Salary sections) and 03_FRONTEND_PAGES.md (Pages 6-8) and 04_BUSINESS_LOGIC.md (Sections 2-4).
BACKEND:
1. Create backend/src/routes/staff.js (all protected):
- GET /api/staff — list all staff, filter by is_active query param
- POST /api/staff — add staff, auto-generate staff_code (pattern in 04_BUSINESS_LOGIC.md Section 2)
- PUT /api/staff/:id — update staff details
- PATCH /api/staff/:id/toggle — flip is_active (1→0 or 0→1)
2. Create backend/src/routes/attendance.js (all protected):
- GET /api/attendance?date=YYYY-MM-DD — return all active staff with their attendance status for the date (LEFT JOIN attendance ON staff_id and date). Null status if not marked.
- POST /api/attendance/bulk — save array of {staff_id, status} for a given date. Use INSERT ... ON DUPLICATE KEY UPDATE (upsert). See 04_BUSINESS_LOGIC.md Section 3.
- GET /api/attendance/summary/:staffId?month=3&year=2026 — count present/absent/half for the month, return with day-by-day records
3. Create backend/src/routes/salary.js (all protected):
- GET /api/salary — list payments, filter by staff_id and/or payment_month
- POST /api/salary — create payment record (all fields from schema)
- GET /api/salary/calculate/:staffId?month=3&year=2026 — compute salary using formula: (monthly_salary / 26) × effectiveDays where effectiveDays = present + (half × 0.5). See 04_BUSINESS_LOGIC.md Section 4.
4. Update dashboard route: update staff_present to query COUNT from attendance where attendance_date = today AND status = 'present'
5. Mount all routes in server.js
FRONTEND:
6. Build frontend/src/pages/Staff.tsx:
- Table: Staff Code, Name, Role, Mobile, Joining Date, Salary (₹ format), Status (Active/Inactive toggle), Actions (Edit)
- "+ Add Staff" button → opens Add Staff modal
- Search by name (filter on frontend)
- Add/Edit Staff modal fields: Full Name*, Role*, Mobile No., Date of Joining, Monthly Salary ₹*, Active toggle
- Toggle active: calls PATCH /api/staff/:id/toggle, refreshes list
7. Build frontend/src/pages/Attendance.tsx:
- Date picker at top (default today, formatted DD/MM/YYYY)
- On date change: fetch GET /api/attendance?date=YYYY-MM-DD
- Attendance grid: each row = staff member with 3 radio buttons (Present / Absent / Half Day)
- Pre-fill radio from fetched status (null = nothing selected yet)
- "Save Attendance" button → POST /api/attendance/bulk with all rows
- Show success toast "Attendance saved for DD/MM/YYYY"
- Below table: clickable "View Monthly Summary" per staff → modal showing month grid and counts
8. Build frontend/src/pages/Salary.tsx — two sections:
Section A (New Payment form, top):
- Select Staff * (dropdown, active staff)
- Payment Month * (text input e.g. "March 2026")
- "Calculate" button → calls GET /api/salary/calculate/:id?month=&year= → fills Days Present and Calculated Salary (read-only display)
- Payment Amount ₹ * (pre-filled from calculated, editable override)
- Payment Type * (Full Salary / Advance / Bonus / Deduction)
- Payment Mode * (Cash / Bank / UPI)
- Notes (optional)
- "Record Payment" button → POST /api/salary, show toast, reset form
Section B (History table, below):
- Filter by Staff (dropdown) and Month (text)
- Table: Date | Staff | Month | Type | Mode | Amount | Notes
TESTING I WILL RUN:
- Add a new staff member, verify staff_code auto-generates
- Mark today's attendance for all staff, save, reload — verify it persists
- Calculate salary for a staff member (March 2026), verify formula: monthly_salary/26 × days
- Record a salary payment, verify it appears in history
- Dashboard staff_present count updates
```
---
## PHASE 4 PROMPT — Parts, Sales & Cash Ledger
```
Phase 3 is complete. Build Phase 4 — Parts Inventory, Parts Sales POS, and Cash Ledger.
Read 02_API_REFERENCE.md (Parts, Parts Sales, Cash Ledger sections) and 03_FRONTEND_PAGES.md (Pages 9-11) and 04_BUSINESS_LOGIC.md (Sections 5-6).
BACKEND:
1. Create backend/src/routes/parts.js (all protected):
- GET /api/parts — list all parts. Include is_low flag (stock_qty <= low_stock_alert). Filter: ?category=&low_stock=true
- POST /api/parts — add part, auto-generate part_code (pattern in 04_BUSINESS_LOGIC.md Section 5)
- PUT /api/parts/:id — update part details
- PATCH /api/parts/:id/stock — manual stock adjustment: UPDATE parts SET stock_qty = stock_qty + ? WHERE id = ?
2. Create backend/src/routes/partsSales.js (all protected):
- GET /api/parts-sales — list sales, join with parts for name. Filter by date.
- POST /api/parts-sales — CRITICAL:
a. Validate quantity > 0
b. Run: UPDATE parts SET stock_qty = stock_qty - ? WHERE id = ? AND stock_qty >= ?
c. Check affectedRows — if 0, return 400 "Insufficient stock"
d. If stock deducted, insert into parts_sales
e. Return sale record with part name
3. Create backend/src/routes/cashLedger.js (all protected):
- GET /api/cash-ledger — list entries. Filter by from/to date and type.
- POST /api/cash-ledger — create entry. Validate: description is non-empty after trim, amount > 0, entry_type is 'in' or 'out'.
4. Update dashboard route: update today_revenue to include BOTH job revenue AND parts revenue:
SELECT (job_revenue + parts_revenue) as today_revenue where both are summed for today
5. Mount all routes in server.js
FRONTEND:
6. Build frontend/src/pages/Parts.tsx:
- Table: Part Code, Name, Category, Unit, Buy Price (₹), Sell Price (₹), Stock Qty, Low Stock (red badge if is_low), Actions
- "+ Add Part" button → opens Add Part modal
- Filter by Category dropdown, "Show Low Stock Only" toggle
- Search by name
- Restock modal: Adjustment quantity (positive integer), confirm button → calls PATCH /api/parts/:id/stock
- Add/Edit Part modal: all fields from parts table
7. Build frontend/src/pages/PartsSales.tsx:
- Date filter (default today)
- Sales table: Date, Part Name, Qty, Unit Price (₹), Total (₹), Customer, Payment Mode, Job Card (if linked)
- "+ Record Sale" button → New Sale modal:
a. Select Part* (searchable dropdown — shows "Ceramic Coat 9H (Stock: 15)")
b. Link to Job Card? (optional dropdown — show recent active + done job cards)
c. Quantity * (number input — on blur, validate against available stock shown)
d. Unit Price ₹ (auto-fills from part.selling_price, editable)
e. Total Amount ₹ (auto-calculated = qty × unit_price, read-only)
f. Customer Name (optional)
g. Payment Mode * (Cash / UPI / Card)
h. "Record Sale" button → POST /api/parts-sales. If 400 insufficient stock, show error in modal. On success: toast, close modal, refresh table.
8. Build frontend/src/pages/CashLedger.tsx:
- Date range filter (from/to, default today to today)
- Ledger table: Date, Type (IN/OUT Badge), Category, Description, Amount (₹)
- Two buttons top right: "+ Cash In" and "+ Cash Out"
- Both open same modal (pre-selects type based on which button):
a. Entry Type (pre-filled, not editable)
b. Amount ₹ *
c. Category * (dropdown: Utilities, Supplies, Advance, Refund, Other)
d. Description * (mandatory text)
e. Date & Time (auto-filled with now, editable)
f. "Save Entry" button
TESTING I WILL RUN:
- Add a new part, verify part_code auto-generates
- Record a parts sale: select part, quantity 3, verify stock decreases by 3
- Try to sell more than available stock — verify error message shows
- Enter a Cash In entry, verify it appears in table
- Enter a Cash Out entry (utility bill), verify it appears
- Dashboard today_revenue now includes parts sales
```
---
## PHASE 5 PROMPT — Accounts, Reports & Exports
```
Phase 4 is complete. Build Phase 5 — Accounts, Daily Report, and Excel Exports.
Read 02_API_REFERENCE.md (Accounts, Reports, Exports sections), 03_FRONTEND_PAGES.md (Pages 12-14), and 04_BUSINESS_LOGIC.md (Sections 7-9).
BACKEND:
1. Install SheetJS: npm install xlsx (in backend/)
2. Create backend/src/utils/excel.js — helper function createExcelResponse(res, filename, sheetsData) that:
- Takes sheetsData as array of {sheetName, data (array of objects)}
- Creates XLSX workbook with XLSX.utils.book_new()
- For each sheet: XLSX.utils.json_to_sheet(data) → book_append_sheet
- Writes buffer: XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' })
- Sets Content-Type and Content-Disposition headers
- Sends buffer response
3. Create backend/src/routes/accounts.js (protected):
- GET /api/accounts/summary?from=YYYY-MM-DD&to=YYYY-MM-DD (default: today to today):
Run 5 separate SQL queries (see 04_BUSINESS_LOGIC.md Section 7) for:
job_revenue, parts_revenue, cash_in_other, staff_payments, cash_out_other
Return structured JSON with income object, expense object, net_balance
- GET /api/accounts/transactions?from=&to= — unified list of all transactions:
UNION of completed jobs (type:'job'), parts sales (type:'parts'), cash_in (type:'cash_in'), cash_out (type:'cash_out'), salary payments (type:'salary')
Each row: date, description, amount, type, reference (job_no or part_code etc.)
ORDER BY date DESC
4. Create backend/src/routes/reports.js (protected):
- GET /api/reports/daily?date=YYYY-MM-DD — return JSON with all 6 sections of the daily report data
- GET /api/reports/daily/download?date=YYYY-MM-DD — generate and stream Excel file:
Use createExcelResponse with 6 sheets per 04_BUSINESS_LOGIC.md Section 8
Filename format: SwishGMS_DailyReport_DD-MM-YYYY.xlsx
5. Create backend/src/routes/exports.js (protected):
- GET /api/exports/job-cards?from=&to= — all job cards as xlsx (join staff name)
Columns: Job No, Type, Reg No, Car Name, Owner, Mobile, Service, Staff, Estimated ₹, Final ₹, Payment Mode, Status, Job Date, Closed At
- GET /api/exports/staff-attendance?month=&year= — two sheets:
Sheet 1: Staff Master (all staff columns)
Sheet 2: Attendance (staff_name, date, status) for the given month
- GET /api/exports/parts-inventory — all parts as xlsx
Columns: Part Code, Name, Category, Unit, Buy Price, Sell Price, Stock Qty, Low Stock Alert
- GET /api/exports/accounts?from=&to= — accounts ledger as xlsx
Same columns as accounts/transactions response, formatted nicely
6. Update dashboard route: update cash_balance to sum all cash flows for today:
cash_balance = job_revenue + parts_revenue + cash_in_other - staff_payments - cash_out_other
7. Mount all routes in server.js
FRONTEND:
8. Build frontend/src/pages/Accounts.tsx:
- Date range filter at top with presets: "Today" | "This Week" | "This Month" | "Custom"
- 3 summary cards: Total Income (₹, green) | Total Expense (₹, red) | Net Balance (₹, blue)
- Two breakdown tables side by side:
Income: Job Revenue | Parts Sales | Cash In (Other) | Total
Expenses: Staff Payments | Cash Out (Other) | Total
- "All Transactions" tab below showing unified list from /api/accounts/transactions
Table: Date, Type (badge), Description, Amount (₹), Reference
9. Build frontend/src/pages/DailyReport.tsx:
- Date picker (default today)
- On date change: fetch GET /api/reports/daily?date=YYYY-MM-DD
- Render on-screen preview:
a. Header card: "Swish Auto Care — Daily Report" | date | Total Income | Total Expense | Net Balance
b. Section tables: Completed Jobs, Parts Sales, Cash In, Cash Out, Staff Payments
c. Day Totals row at bottom
- "Download Excel" button (primary, prominent):
Calls downloadExcel() helper (see 03_FRONTEND_PAGES.md Excel Download Pattern) with /api/reports/daily/download?date= and filename SwishGMS_DailyReport_DD-MM-YYYY.xlsx
10. Build frontend/src/pages/Exports.tsx:
- 4 export cards in a 2×2 grid
- Card 1 — Job Cards: From Date + To Date pickers + "Download xlsx" button
- Card 2 — Staff & Attendance: Month selector (1-12) + Year (2026) + "Download xlsx" button
- Card 3 — Parts Inventory: Just a "Download xlsx" button (no date filter needed)
- Card 4 — Accounts Ledger: From Date + To Date pickers + "Download xlsx" button
- Each download button uses the downloadExcel() helper with responseType: 'blob' and triggers browser file save
- Show loading state on button while downloading (spinner, disable button)
11. Create frontend/src/utils/download.ts with the downloadExcel helper function:
```ts
export async function downloadExcel(api: AxiosInstance, url: string, filename: string, params?: object) {
const response = await api.get(url, { params, responseType: 'blob' });
const blob = new Blob([response.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
}
```
TESTING I WILL RUN:
- Accounts page loads with today's summary (income, expense, balance)
- Change date range to "This Month" — totals update
- Daily Report page shows all sections for today
- Download daily report — verify it opens in Excel with correct data
- All 4 Export buttons produce downloadable .xlsx files with correct data
- Dashboard cash_balance now shows a real number
```
---
## PHASE 6 PROMPT — Polish, Seed Data & Final Testing
```
Phase 5 is complete. Build Phase 6 — Final Polish, Seed Data, and Complete the Demo.
SEED DATA (create backend/src/config/fullSeed.js):
Create a comprehensive seed that inserts:
1. Admin user (username: admin, password: swish@2024, bcrypt hashed)
2. 4 staff members (already seeded in Phase 1, skip if exists)
3. 5 parts (already seeded, skip if exists)
4. 8 job cards across the last 7 days (mix of full and wash, some active some done):
- 3 active job cards (today)
- 5 done job cards (last 7 days, with final_amount and payment_mode)
5. Attendance for all 4 staff for the last 14 days (random mix of present/absent/half)
6. 2 salary payments (one salary, one advance)
7. 10 parts sales entries spread over last 7 days (various parts, quantities)
8. 12 cash ledger entries (mix of in/out, various categories)
Run this with: npm run fullseed (add to package.json)
UI POLISH tasks:
1. Add loading skeleton to all data tables (show 5 gray placeholder rows while fetching)
2. Add empty state components: when table has no data, show icon + message + action button
Example: Job Cards empty → "No job cards yet" + "Create First Job Card" button
3. Add confirmation dialog before "Mark as Done" — "Are you sure? This cannot be undone."
4. Add toast notifications to ALL create/update actions that don't already have them:
- Staff add/edit: "Staff added successfully"
- Attendance save: "Attendance saved for DD/MM/YYYY"
- Salary payment: "Payment of ₹X recorded for [Staff Name]"
- Part add: "Part added to inventory"
- Restock: "Stock updated for [Part Name]"
- Cash entry: "Cash [In/Out] entry saved"
5. Fix all currency displays to use ₹ with Indian comma formatting (en-IN locale)
6. Fix all date displays to use DD/MM/YYYY format
7. Add active nav item highlight in Sidebar (bold + background for current route)
8. Make sure forms clear/reset after successful submission
9. Add "Required field" validation error messages below fields on submit with empty required fields
10. In Job Cards table, make Reg No uppercase on input
FINAL VERIFICATION — Run through all 10 Acceptance Criteria:
AC-01: Login with admin/swish@2024 → dashboard loads ✓
AC-02: Create Full job card + Quick Wash job card ✓
AC-03: Job shows as Active, mark Done with amount+payment ✓
AC-04: Mark attendance for all staff, save, reload ✓
AC-05: Log salary payment, view in history ✓
AC-06: Record parts sale, stock decreases ✓
AC-07: Cash in and cash out entries ✓
AC-08: Accounts shows today's income/expense/balance ✓
AC-09: Download daily report as Excel ✓
AC-10: All 4 export buttons produce Excel files ✓
Report any AC that fails and fix it.
Final output should be the complete running app with:
- Backend on http://localhost:5000
- Frontend on http://localhost:5173
- Login: admin / swish@2024
- All 10 ACs passing
- Realistic demo data preloaded
```
---
## TROUBLESHOOTING PROMPTS
Use these if specific things break:
### If MySQL connection fails:
```
The MySQL connection is failing. Debug this:
1. Check backend/.env has correct DB_USER, DB_PASS, DB_NAME
2. Verify MySQL is running: mysql -u root -p
3. Verify database exists: SHOW DATABASES; (should show swish_gms)
4. Check backend/src/config/db.js uses mysql2/promise createPool correctly
5. Test the pool with a simple query: SELECT 1 as test
Show me the exact error and fix it.
```
### If CORS error in browser:
```
There's a CORS error in the browser console. Fix it in backend/src/server.js:
app.use(cors({
origin: 'http://localhost:5173',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
Make sure cors() is called BEFORE any route definitions.
```
### If Excel download doesn't work:
```
The Excel download is not working. Check:
1. Frontend axios call uses responseType: 'blob'
2. Backend sets correct Content-Type header: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
3. Backend sets Content-Disposition: attachment; filename="..."
4. XLSX.write uses { type: 'buffer', bookType: 'xlsx' }
5. res.send(buffer) not res.json(buffer)
Show me the current code and fix it.
```
### If JWT auth stops working:
```
JWT authentication is broken — getting 401 on all protected routes. Debug:
1. Check frontend axios.ts request interceptor attaches 'Bearer <token>' header
2. Check localStorage has the key 'gms_token' (open DevTools → Application → Local Storage)
3. Check backend auth.js extracts token with: authHeader.split(' ')[1]
4. Check JWT_SECRET in .env matches what was used to sign the token
5. Log the token on backend: console.log('Token:', token) and verify it's not undefined
```
### If stock deduction race condition:
```
Parts stock is going negative. Fix the stock deduction in partsSales route:
Use atomic UPDATE with stock check:
const [result] = await db.query(
'UPDATE parts SET stock_qty = stock_qty - ? WHERE id = ? AND stock_qty >= ?',
[quantity, part_id, quantity]
);
if (result.affectedRows === 0) {
return res.status(400).json({ error: 'Insufficient stock' });
}
// Only insert sale AFTER successful stock deduction
```