Initial import
This commit is contained in:
commit
29f8d58256
157
00_PROJECT_OVERVIEW.md
Normal file
157
00_PROJECT_OVERVIEW.md
Normal 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
289
01_DATABASE_SCHEMA.md
Normal 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
480
02_API_REFERENCE.md
Normal 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®_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
413
03_FRONTEND_PAGES.md
Normal 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
337
04_BUSINESS_LOGIC.md
Normal 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
289
05_IMPLEMENTATION_PLAN.md
Normal 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
532
06_AGENT_PROMPTS.md
Normal 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 (M1–M10)
|
||||
- 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
|
||||
```
|
||||
Loading…
x
Reference in New Issue
Block a user