From 29f8d58256e619fd9aae015fe7eac1975d751ec3 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 24 Mar 2026 15:28:07 +0000 Subject: [PATCH] Initial import --- 00_PROJECT_OVERVIEW.md | 157 +++++++++++ 01_DATABASE_SCHEMA.md | 289 +++++++++++++++++++++ 02_API_REFERENCE.md | 480 ++++++++++++++++++++++++++++++++++ 03_FRONTEND_PAGES.md | 413 +++++++++++++++++++++++++++++ 04_BUSINESS_LOGIC.md | 337 ++++++++++++++++++++++++ 05_IMPLEMENTATION_PLAN.md | 289 +++++++++++++++++++++ 06_AGENT_PROMPTS.md | 532 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 2497 insertions(+) create mode 100644 00_PROJECT_OVERVIEW.md create mode 100644 01_DATABASE_SCHEMA.md create mode 100644 02_API_REFERENCE.md create mode 100644 03_FRONTEND_PAGES.md create mode 100644 04_BUSINESS_LOGIC.md create mode 100644 05_IMPLEMENTATION_PLAN.md create mode 100644 06_AGENT_PROMPTS.md diff --git a/00_PROJECT_OVERVIEW.md b/00_PROJECT_OVERVIEW.md new file mode 100644 index 0000000..8f1689e --- /dev/null +++ b/00_PROJECT_OVERVIEW.md @@ -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 | diff --git a/01_DATABASE_SCHEMA.md b/01_DATABASE_SCHEMA.md new file mode 100644 index 0000000..8ee11d2 --- /dev/null +++ b/01_DATABASE_SCHEMA.md @@ -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); +``` diff --git a/02_API_REFERENCE.md b/02_API_REFERENCE.md new file mode 100644 index 0000000..f9320ca --- /dev/null +++ b/02_API_REFERENCE.md @@ -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 ` 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 diff --git a/03_FRONTEND_PAGES.md b/03_FRONTEND_PAGES.md new file mode 100644 index 0000000..3dd062c --- /dev/null +++ b/03_FRONTEND_PAGES.md @@ -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 + +### `` +Colored pill badge for statuses. + +### `children` +Overlay modal with close button. + +### `` +Reusable table with loading skeleton. + +### `` +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 + 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; +``` diff --git a/04_BUSINESS_LOGIC.md b/04_BUSINESS_LOGIC.md new file mode 100644 index 0000000..88ba343 --- /dev/null +++ b/04_BUSINESS_LOGIC.md @@ -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' }); +} +``` diff --git a/05_IMPLEMENTATION_PLAN.md b/05_IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..a8ff639 --- /dev/null +++ b/05_IMPLEMENTATION_PLAN.md @@ -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 `
Page Name
`) +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 `` component +7. Build shared `` 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 diff --git a/06_AGENT_PROMPTS.md b/06_AGENT_PROMPTS.md new file mode 100644 index 0000000..913f635 --- /dev/null +++ b/06_AGENT_PROMPTS.md @@ -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
PageName Page
) 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 ' 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 +```