{"id":19056341,"url":"https://github.com/chanmeng666/automotive-repair-management-system","last_synced_at":"2026-03-16T23:34:50.151Z","repository":{"id":233084403,"uuid":"745417204","full_name":"ChanMeng666/automotive-repair-management-system","owner":"ChanMeng666","description":"【Star-crossed coders unite!⭐️】A comprehensive web-based automotive repair shop management system built with Flask and MySQL. Features separate interfaces for technicians and administrators to manage repair jobs, customers, parts inventory, and billing.","archived":false,"fork":false,"pushed_at":"2025-01-15T04:05:37.000Z","size":235,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-15T06:10:17.955Z","etag":null,"topics":["automotive","bootstrap","business-management","flask","mysql","python","repair-shop","web-application"],"latest_commit_sha":null,"homepage":"https://chanmeng.pythonanywhere.com/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ChanMeng666.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":"chanmeng66u","thanks_dev":null,"custom":null}},"created_at":"2024-01-19T09:41:34.000Z","updated_at":"2025-01-15T04:05:38.000Z","dependencies_parsed_at":"2024-12-27T09:22:56.785Z","dependency_job_id":"87dbb536-b377-487e-aace-6912af5e94b6","html_url":"https://github.com/ChanMeng666/automotive-repair-management-system","commit_stats":null,"previous_names":["chanmeng666/spb","chanmeng666/automotive-repair-management-system"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChanMeng666%2Fautomotive-repair-management-system","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChanMeng666%2Fautomotive-repair-management-system/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChanMeng666%2Fautomotive-repair-management-system/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChanMeng666%2Fautomotive-repair-management-system/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ChanMeng666","download_url":"https://codeload.github.com/ChanMeng666/automotive-repair-management-system/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240112399,"owners_count":19749639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automotive","bootstrap","business-management","flask","mysql","python","repair-shop","web-application"],"created_at":"2024-11-08T23:49:07.687Z","updated_at":"2026-03-16T23:34:50.138Z","avatar_url":"https://github.com/ChanMeng666.png","language":"HTML","funding_links":["https://buymeacoffee.com/chanmeng66u"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\u003ca name=\"readme-top\"\u003e\u003c/a\u003e\n\n[![Project Banner](./public/RepairOS-logo.svg)](#)\n\n# RepairOS\u003cbr/\u003e\u003ch3\u003eMulti-Tenant Automotive Repair Management Platform\u003c/h3\u003e\n\nA production-ready SaaS platform for automotive repair shop management, featuring multi-tenant architecture with shared-schema isolation, role-based access control, Stripe subscription billing, and a \"Precision Industrial\" design system.\u003cbr/\u003e\nBuilt with **Flask 3.1**, **SQLAlchemy 2.0**, **Neon PostgreSQL**, **Google OAuth**, and **Bootstrap 5.3**.\u003cbr/\u003e\nOne-click deployment to **Heroku** with cloud-native database on **Neon**.\n\n[Live Demo][project-link] · [Documentation](docs/) · [Report Bug][github-issues-link] · [Request Feature][github-issues-link]\n\n\u003cbr/\u003e\n\n[![Visit Live Demo](https://img.shields.io/badge/🔧_Visit_Live_Demo-1e3a5f?style=for-the-badge\u0026logoColor=white)][project-link]\n\n\u003cbr/\u003e\n\n\u003c!-- SHIELD GROUP --\u003e\n\n[![][python-shield]][python-link]\n[![][flask-shield]][flask-link]\n[![][sqlalchemy-shield]][sqlalchemy-link]\n[![][postgresql-shield]][postgresql-link]\n[![][stripe-shield]][stripe-link]\n[![][heroku-shield]][heroku-link]\n[![][bootstrap-shield]][bootstrap-link]\n[![][license-shield]][license-link]\n\n\u003c!-- QUICK ACTION BUTTONS --\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/ChanMeng666/automotive-repair-management-system/stargazers\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/⭐_Star_This_Repo-FFD700?style=for-the-badge\u0026logo=github\u0026logoColor=black\" alt=\"Star this repo\"/\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n**Share Project Repository**\n\n[![][share-x-shield]][share-x-link]\n[![][share-linkedin-shield]][share-linkedin-link]\n[![][share-reddit-shield]][share-reddit-link]\n[![][share-telegram-shield]][share-telegram-link]\n\n\u003csup\u003eBuilt for the next generation of automotive repair shop management.\u003c/sup\u003e\n\n**Tech Stack:**\n\n\u003cdiv align=\"center\"\u003e\n\n \u003cimg src=\"https://img.shields.io/badge/python-3670A0?style=for-the-badge\u0026logo=python\u0026logoColor=ffdd54\"/\u003e\n \u003cimg src=\"https://img.shields.io/badge/flask-%23000000.svg?style=for-the-badge\u0026logo=flask\u0026logoColor=white\"/\u003e\n \u003cimg src=\"https://img.shields.io/badge/postgresql-%234169E1.svg?style=for-the-badge\u0026logo=postgresql\u0026logoColor=white\"/\u003e\n \u003cimg src=\"https://img.shields.io/badge/stripe-%23635BFF.svg?style=for-the-badge\u0026logo=stripe\u0026logoColor=white\"/\u003e\n \u003cimg src=\"https://img.shields.io/badge/bootstrap-%237952B3.svg?style=for-the-badge\u0026logo=bootstrap\u0026logoColor=white\"/\u003e\n \u003cimg src=\"https://img.shields.io/badge/heroku-%23430098.svg?style=for-the-badge\u0026logo=heroku\u0026logoColor=white\"/\u003e\n \u003cimg src=\"https://img.shields.io/badge/chart.js-%23FF6384.svg?style=for-the-badge\u0026logo=chartdotjs\u0026logoColor=white\"/\u003e\n\n\u003c/div\u003e\n\n\u003c/div\u003e\n\n\u003e [!IMPORTANT]\n\u003e This project is a comprehensive multi-tenant SaaS platform that combines Flask backend with a modern Bootstrap 5.3 frontend. It features shared-schema multi-tenancy, 6-role RBAC, Stripe subscription billing, Google OAuth, 4-step onboarding, inventory management, and a full-featured admin/technician portal with real-time analytics.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ckbd\u003e📑 Table of Contents\u003c/kbd\u003e\u003c/summary\u003e\n\n#### TOC\n\n- [🌟 Introduction](#-introduction)\n- [✨ Key Features](#-key-features)\n  - [`1` Multi-Tenant SaaS Architecture](#1-multi-tenant-saas-architecture)\n  - [`2` Role-Based Dual Portals](#2-role-based-dual-portals)\n  - [`3` Stripe Subscription Billing](#3-stripe-subscription-billing)\n  - [`*` Additional Features](#-additional-features)\n- [🛠️ Tech Stack](#️-tech-stack)\n- [🏗️ Architecture](#️-architecture)\n- [📊 Database Schema](#-database-schema)\n- [🚀 Getting Started](#-getting-started)\n- [🛳 Deployment](#-deployment)\n- [📖 API Reference](#-api-reference)\n- [⌨️ Development](#️-development)\n- [🤝 Contributing](#-contributing)\n- [📄 License](#-license)\n- [🙋‍♀️ Author](#️-author)\n\n####\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003c!-- ═══════════════════════════════════════════════════════════════════════════\n     SECTION: Introduction\n     ═══════════════════════════════════════════════════════════════════════════ --\u003e\n\n## 🌟 Introduction\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\n\u003ch4\u003eAbout This Project\u003c/h4\u003e\n\nRepairOS is a production-grade multi-tenant SaaS platform purpose-built for automotive repair shops. It enables repair businesses to manage work orders, customers, billing, inventory, and team members through a single unified platform with complete tenant data isolation.\n\nThe platform supports multiple organizations on a shared database schema using `tenant_id` discriminator columns, with automatic query filtering via custom SQLAlchemy mixins. Each organization gets its own isolated workspace with configurable service catalogs, parts inventory, and team roles.\n\n\u003ch4\u003eWhy This Project?\u003c/h4\u003e\n\nAutomotive repair shops often rely on fragmented tools — paper work orders, spreadsheets for billing, separate inventory systems. This platform consolidates all operations into one cohesive system with:\n\n- **Multi-tenant isolation** so multiple shops can share one deployment\n- **Role-based access** so technicians and administrators see only what they need\n- **Stripe billing** for SaaS monetization with subscription plans\n- **4-step onboarding** to get new shops operational in minutes\n\n\u003ch4\u003eGoals\u003c/h4\u003e\n\n- Deliver a complete, deployable SaaS reference architecture using Flask\n- Demonstrate shared-schema multi-tenancy with automatic data isolation\n- Provide a modern, responsive UI following the \"Precision Industrial\" design language\n- Enable one-click cloud deployment with Heroku + Neon PostgreSQL\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n\u003e [!NOTE]\n\u003e - Python 3.9+ required\n\u003e - Neon PostgreSQL account required for cloud database (SQLite used for local testing)\n\u003e - Google Cloud Console account optional (for OAuth sign-in)\n\u003e - Stripe account optional (for subscription billing)\n\n| [![][demo-shield-badge]][project-link] | No installation required! Visit the live demo to experience it firsthand. |\n| :------------------------------------- | :------------------------------------------------------------------------ |\n\n\u003cdiv align=\"right\"\u003e\n\n[![][back-to-top]](#readme-top)\n\n\u003c/div\u003e\n\n\u003c!-- ═══════════════════════════════════════════════════════════════════════════\n     SECTION: Key Features\n     ═══════════════════════════════════════════════════════════════════════════ --\u003e\n\n## ✨ Key Features\n\n### `1` Multi-Tenant SaaS Architecture\n\nComplete shared-schema multi-tenancy with automatic data isolation. Each organization operates in its own workspace with independent service catalogs, customer records, and team management — all on a single database deployment.\n\nKey capabilities include:\n- 🏢 **Organization Management**: Create and configure multiple repair shops with custom settings\n- 🔒 **Data Isolation**: `TenantScopedMixin` automatically filters all queries by `tenant_id`\n- 👥 **Team Invitations**: Invite team members with role-based permissions\n- 🚀 **4-Step Onboarding**: Guided setup for business details, service catalog, parts catalog, and team\n- 🌐 **Tenant-Scoped URLs**: Routes available at both `/technician/...` and `/org/\u003cslug\u003e/technician/...`\n\n[![][back-to-top]](#readme-top)\n\n### `2` Role-Based Dual Portals\n\nTwo distinct portal experiences for Technicians and Administrators, controlled by a 6-role RBAC system defined on `TenantMembership`:\n\n**Technician Portal:**\n- 📋 Work order management with pagination\n- 🔧 Add services and parts to jobs with quantity tracking\n- 💰 Real-time total cost calculation\n- ✅ Job completion workflow\n\n**Administrator Portal:**\n- 👤 Customer management with search (first name, family name, or both)\n- 💳 Billing management with overdue tracking (14-day threshold)\n- 📦 Service \u0026 parts catalog management with categories and descriptions\n- 📊 Inventory tracking with reorder alerts and stock adjustments\n- 👥 Team member management with role assignment\n- 📈 Dashboard with Chart.js analytics (monthly revenue, job status distribution)\n- ⚙️ Organization settings and subscription management\n\n**RBAC Roles:**\n\n| Role | Key Permissions |\n|------|----------------|\n| `owner` | Full access including organization management |\n| `admin` | User management, catalog, inventory, jobs, billing, reports |\n| `manager` | Jobs, customers, billing, reports |\n| `technician` | Jobs, reports |\n| `parts_clerk` | Catalog, inventory, reports |\n| `viewer` | Reports only |\n\n[![][back-to-top]](#readme-top)\n\n### `3` Stripe Subscription Billing\n\nIntegrated SaaS billing with Stripe for subscription management:\n\n- 💎 **4 Plans**: Free, Starter ($29/mo), Professional ($79/mo), Enterprise ($199/mo)\n- 🎁 **14-Day Trial**: Free trial period for new organizations\n- 🛒 **Stripe Checkout**: Hosted payment pages for secure card processing\n- 🔄 **Billing Portal**: Customer self-service for plan changes and payment methods\n- 📡 **Webhook Handling**: Automatic subscription status updates on payment events\n\n[![][back-to-top]](#readme-top)\n\n### `*` Additional Features\n\n- [x] 🔐 **Google OAuth 2.0**: One-click sign-in via Authlib integration\n- [x] 🔑 **JWT Authentication**: Optional Neon Auth (Better Auth) JWT verification\n- [x] 🛡️ **CSRF Protection**: Token-based CSRF on all state-changing requests\n- [x] 🧹 **Input Sanitization**: XSS prevention and SQL injection scanning\n- [x] 🔒 **Security Headers**: HSTS, X-Frame-Options, X-Content-Type-Options\n- [x] 🔐 **Password Security**: PBKDF2 hashing with 100,000 iterations\n- [x] 🎨 **Precision Industrial Design**: Steel blue (#1e3a5f) + signal orange (#e85d04) palette\n- [x] 📱 **Responsive Layout**: Mobile-first with breakpoints at 768px and 480px\n- [x] 📊 **Chart.js Dashboards**: Monthly revenue line charts, job status doughnut charts\n- [x] 🔍 **Global Search**: Async customer search with API integration\n- [x] ⌨️ **Keyboard Shortcuts**: Ctrl+K for search, Esc for close\n- [x] 🔔 **Toast Notifications**: Real-time feedback for user actions\n- [x] 📄 **Alembic Migrations**: Versioned database schema migrations\n\n\u003e ✨ More features are continuously being added as the project evolves.\n\n\u003cdiv align=\"right\"\u003e\n\n[![][back-to-top]](#readme-top)\n\n\u003c/div\u003e\n\n\u003c!-- ═══════════════════════════════════════════════════════════════════════════\n     SECTION: Tech Stack\n     ═══════════════════════════════════════════════════════════════════════════ --\u003e\n\n## 🛠️ Tech Stack\n\n\u003cdiv align=\"center\"\u003e\n  \u003ctable\u003e\n    \u003ctr\u003e\n      \u003ctd align=\"center\" width=\"96\"\u003e\n        \u003cimg src=\"https://cdn.simpleicons.org/python\" width=\"48\" height=\"48\" alt=\"Python\" /\u003e\n        \u003cbr\u003ePython 3.9+\n      \u003c/td\u003e\n      \u003ctd align=\"center\" width=\"96\"\u003e\n        \u003cimg src=\"https://cdn.simpleicons.org/flask\" width=\"48\" height=\"48\" alt=\"Flask\" /\u003e\n        \u003cbr\u003eFlask 3.1.3\n      \u003c/td\u003e\n      \u003ctd align=\"center\" width=\"96\"\u003e\n        \u003cimg src=\"https://cdn.simpleicons.org/postgresql\" width=\"48\" height=\"48\" alt=\"PostgreSQL\" /\u003e\n        \u003cbr\u003eNeon PG\n      \u003c/td\u003e\n      \u003ctd align=\"center\" width=\"96\"\u003e\n        \u003cimg src=\"https://cdn.simpleicons.org/stripe\" width=\"48\" height=\"48\" alt=\"Stripe\" /\u003e\n        \u003cbr\u003eStripe\n      \u003c/td\u003e\n      \u003ctd align=\"center\" width=\"96\"\u003e\n        \u003cimg src=\"https://cdn.simpleicons.org/bootstrap\" width=\"48\" height=\"48\" alt=\"Bootstrap\" /\u003e\n        \u003cbr\u003eBootstrap 5.3\n      \u003c/td\u003e\n      \u003ctd align=\"center\" width=\"96\"\u003e\n        \u003cimg src=\"https://cdn.simpleicons.org/chartdotjs\" width=\"48\" height=\"48\" alt=\"Chart.js\" /\u003e\n        \u003cbr\u003eChart.js 4.4\n      \u003c/td\u003e\n      \u003ctd align=\"center\" width=\"96\"\u003e\n        \u003cimg src=\"https://cdn.simpleicons.org/heroku\" width=\"48\" height=\"48\" alt=\"Heroku\" /\u003e\n        \u003cbr\u003eHeroku\n      \u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/table\u003e\n\u003c/div\u003e\n\n**Backend:**\n- **Framework**: Flask 3.1.3 with application factory pattern\n- **ORM**: SQLAlchemy 2.0.36 with custom model mixins\n- **Database**: Neon PostgreSQL (cloud) / SQLite (testing)\n- **Migrations**: Alembic 1.14.0\n- **Authentication**: Authlib 1.6.6 (Google OAuth) + PyJWT 2.10.1 (Neon Auth)\n- **Payments**: Stripe 11.4.1 (subscriptions, checkout, webhooks)\n- **WSGI Server**: Gunicorn 23.0.0\n\n**Frontend:**\n- **Framework**: Bootstrap 5.3 with custom CSS design system\n- **Charts**: Chart.js 4.4.0 for data visualization\n- **Icons**: Lucide Icons (CDN, client-side rendering)\n- **Typography**: DM Sans + Source Sans 3 + JetBrains Mono (Google Fonts)\n- **JavaScript**: Vanilla ES6+ (no build tools required)\n\n**DevOps:**\n- **Deployment**: Heroku with Procfile (Gunicorn)\n- **Database**: Neon PostgreSQL (serverless, auto-scaling)\n- **Code Quality**: Black (formatting), isort (imports), flake8 (linting), mypy (types)\n- **Testing**: pytest + pytest-cov (70% minimum threshold)\n\n\u003e [!TIP]\n\u003e Each technology was selected for production readiness, simplicity, and Flask ecosystem compatibility. No frontend build step is required — static assets are served directly by Flask.\n\n\u003cdiv align=\"right\"\u003e\n\n[![][back-to-top]](#readme-top)\n\n\u003c/div\u003e\n\n\u003c!-- ═══════════════════════════════════════════════════════════════════════════\n     SECTION: Architecture\n     ═══════════════════════════════════════════════════════════════════════════ --\u003e\n\n## 🏗️ Architecture\n\n\u003e [!TIP]\n\u003e The architecture follows Flask best practices with a clear separation of concerns: views handle HTTP, services encapsulate business logic, and models manage data access with automatic tenant scoping.\n\n\u003ctable\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003eHigh-Level Architecture\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n```\n┌─────────────────────────────────────────────────────────────────────┐\n│                         Client (Browser)                             │\n│     Bootstrap 5.3 · Lucide Icons · Chart.js 4.4 · Vanilla ES6+     │\n└────────────────────────────────┬────────────────────────────────────┘\n                                 │\n┌────────────────────────────────┴────────────────────────────────────┐\n│                     Flask Application (WSGI)                         │\n│                                                                      │\n│  ┌────────────────────────────────────────────────────────────────┐  │\n│  │                     Middleware Layer                            │  │\n│  │   Tenant Context Resolution · Security Headers · CSRF Token    │  │\n│  └────────────────────────────────────────────────────────────────┘  │\n│                                                                      │\n│  ┌────────────────────────────────────────────────────────────────┐  │\n│  │                  Blueprints (Views Layer)                       │  │\n│  │   main · auth · technician · administrator · billing ·         │  │\n│  │   onboarding                                                    │  │\n│  │   Each registered at /prefix and /org/\u003cslug\u003e/prefix             │  │\n│  └────────────────────────────────────────────────────────────────┘  │\n│                                                                      │\n│  ┌────────────────────────────────────────────────────────────────┐  │\n│  │                    Services Layer                               │  │\n│  │   AuthService · NeonAuthService · OAuthService ·                │  │\n│  │   CustomerService · JobService · BillingService ·               │  │\n│  │   TenantService · StripeService                                 │  │\n│  └────────────────────────────────────────────────────────────────┘  │\n│                                                                      │\n│  ┌────────────────────────────────────────────────────────────────┐  │\n│  │                     Models Layer (ORM)                          │  │\n│  │   User · Tenant · TenantMembership · Customer · Job ·          │  │\n│  │   Service · Part · Inventory · Subscription                     │  │\n│  │   ──────────────────────────────────────────                    │  │\n│  │   Mixins: BaseModelMixin · TenantScopedMixin · TimestampMixin  │  │\n│  └────────────────────────────────────────────────────────────────┘  │\n│                                                                      │\n└────────────────────────────────┬────────────────────────────────────┘\n                                 │\n                 ┌───────────────┼───────────────┐\n                 │               │               │\n          ┌──────┴──────┐ ┌─────┴─────┐ ┌───────┴───────┐\n          │   Neon       │ │  Google   │ │    Stripe     │\n          │  PostgreSQL  │ │  OAuth    │ │   Payments    │\n          └─────────────┘ └───────────┘ └───────────────┘\n```\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003eMulti-Tenant Data Flow\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n```\n┌──────────┐     ┌──────────────┐     ┌───────────────────┐     ┌──────────┐\n│  Request  │────\u003e│  Tenant      │────\u003e│  Blueprint View   │────\u003e│  Service │\n│           │     │  Middleware   │     │  (@login_required │     │  Layer   │\n│  /org/    │     │              │     │   @tenant_required│     │          │\n│  \u003cslug\u003e/  │     │  Resolves:   │     │   @permission_    │     │  Fresh   │\n│  tech/    │     │  URL slug    │     │    required)      │     │  instance│\n│  jobs     │     │  Session     │     │                   │     │  per     │\n│           │     │  X-Tenant-ID │     │                   │     │  request │\n└──────────┘     │              │     └───────────────────┘     └────┬─────┘\n                  │  Sets:       │                                    │\n                  │  g.current_  │     ┌───────────────────┐         │\n                  │  tenant_id   │     │  TenantScoped     │\u003c────────┘\n                  └──────────────┘     │  Mixin            │\n                                       │                   │\n                                       │  Auto-filters     │\n                                       │  all queries by   │\n                                       │  g.current_       │\n                                       │  tenant_id        │\n                                       └────────┬──────────┘\n                                                │\n                                       ┌────────┴──────────┐\n                                       │  Neon PostgreSQL   │\n                                       │  (shared schema)   │\n                                       └───────────────────┘\n```\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003eAuthentication Flow\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                   Authentication Methods                      │\n├─────────────────┬──────────────────┬────────────────────────┤\n│                 │                  │                          │\n│  ① Password     │  ② Google OAuth  │  ③ Neon Auth JWT        │\n│  /login POST    │  /auth/google    │  /auth/callback         │\n│                 │       │          │       │                  │\n│  AuthService.   │  Authlib         │  NeonAuthService.       │\n│  authenticate() │  redirect to     │  verify_token()         │\n│       │         │  Google → back   │       │                 │\n│       │         │  to /auth/       │  User.authenticate_     │\n│       │         │  google/callback │  with_jwt()             │\n│       │         │       │          │  (auto-creates user)    │\n│       ▼         │       ▼          │       ▼                 │\n├─────────────────┴──────────────────┴────────────────────────┤\n│                                                              │\n│  Session Established:                                        │\n│  session['logged_in'] = True                                 │\n│  session['user_id'] = user.user_id                          │\n│  session['current_tenant_id'] = tenant.tenant_id            │\n│  session['current_role'] = membership.role                   │\n│                                                              │\n├──────────────────────────────────────────────────────────────┤\n│                                                              │\n│  Multiple Tenants?  ──Yes──\u003e  /auth/select-tenant            │\n│        │                                                     │\n│       No                                                     │\n│        │                                                     │\n│        ▼                                                     │\n│  Redirect based on role:                                     │\n│  admin/owner → /administrator/dashboard                      │\n│  technician  → /technician/current-jobs                      │\n│                                                              │\n└──────────────────────────────────────────────────────────────┘\n```\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003eProject Structure\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n```\nautomotive-repair-management-system/\n├── app/\n│   ├── __init__.py              # Application factory (create_app)\n│   ├── extensions.py            # Flask-SQLAlchemy initialization\n│   ├── models/                  # SQLAlchemy ORM models\n│   │   ├── base.py              # BaseModelMixin, TenantScopedMixin, TimestampMixin\n│   │   ├── user.py              # User model + ROLE_PERMISSIONS dict\n│   │   ├── tenant.py            # Tenant (organization) model\n│   │   ├── tenant_membership.py # User-Tenant relationship with role\n│   │   ├── customer.py          # Customer model (tenant-scoped)\n│   │   ├── job.py               # Job, JobService, JobPart models\n│   │   ├── service.py           # Service catalog (tenant-scoped)\n│   │   ├── part.py              # Parts catalog (tenant-scoped)\n│   │   ├── inventory.py         # Inventory + InventoryTransaction\n│   │   └── subscription.py      # Stripe subscription model\n│   ├── services/                # Business logic layer\n│   │   ├── auth_service.py      # Auth + NeonAuthService (JWT/JWKS)\n│   │   ├── oauth_service.py     # Google OAuth via Authlib\n│   │   ├── job_service.py       # Work order operations\n│   │   ├── customer_service.py  # Customer management\n│   │   ├── billing_service.py   # Billing \u0026 payment logic\n│   │   ├── tenant_service.py    # Organization management + catalog seeding\n│   │   └── stripe_service.py    # Stripe checkout, subscriptions, webhooks\n│   ├── views/                   # Flask blueprints (route handlers)\n│   │   ├── main.py              # Public routes, customer CRUD\n│   │   ├── auth.py              # OAuth, JWT, tenant selection, org registration\n│   │   ├── technician.py        # Technician portal (jobs, services, parts)\n│   │   ├── administrator.py     # Admin portal (customers, billing, catalog, team)\n│   │   ├── billing.py           # Stripe checkout \u0026 webhook handler\n│   │   └── onboarding.py        # 4-step onboarding flow\n│   ├── middleware/\n│   │   └── tenant.py            # Tenant context resolution middleware\n│   ├── utils/\n│   │   ├── decorators.py        # @login_required, @tenant_required, @permission_required\n│   │   ├── security.py          # CSRF, password hashing, input sanitization, SQL injection\n│   │   ├── validators.py        # Email, phone, date, cost, quantity validation\n│   │   ├── error_handler.py     # Custom exceptions + HTTP error handlers\n│   │   └── database.py          # Database exception classes\n│   ├── templates/               # Jinja2 templates (see below)\n│   └── static/                  # CSS, JS, images (no build step)\n├── config/\n│   └── base.py                  # BaseConfig, DevelopmentConfig, ProductionConfig, TestingConfig\n├── migrations/\n│   ├── env.py                   # Alembic environment\n│   └── versions/                # 001-003: multi-tenant schema migrations\n├── tests/\n│   ├── conftest.py              # Fixtures: app, client, authenticated/admin sessions\n│   ├── utils.py                 # Mock builders, test helpers\n│   ├── unit/                    # Auth, models, security, multi-tenant tests\n│   └── integration/             # View/route integration tests\n├── docs/deployment/             # Heroku, Neon, Quick Start guides\n├── run.py                       # Development server entry point\n├── wsgi.py                      # Production WSGI entry point\n├── Procfile                     # Heroku: gunicorn wsgi:application\n├── requirements.txt             # Python dependencies (pinned versions)\n├── pyproject.toml               # Project metadata + tool configs (black, isort, pytest, mypy)\n└── pytest.ini                   # Test configuration with markers\n```\n\n**Template Structure:**\n\n```\napp/templates/\n├── base/layout.html             # Main layout (Precision Industrial design)\n├── base.html                    # Landing page layout\n├── auth/                        # Login, tenant selection, org registration\n├── technician/                  # Dashboard, jobs, services, parts (7 templates)\n├── administrator/               # Dashboard, customers, billing, catalog, team (12 templates)\n├── billing/                     # Plans, checkout success\n├── onboarding/                  # Steps 1-4, completion\n├── components/                  # Flash messages, pagination, spinner, toasts\n└── errors/                      # 404, 403, 500 error pages\n```\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003cdiv align=\"right\"\u003e\n\n[![][back-to-top]](#readme-top)\n\n\u003c/div\u003e\n\n\u003c!-- ═══════════════════════════════════════════════════════════════════════════\n     SECTION: Database Schema\n     ═══════════════════════════════════════════════════════════════════════════ --\u003e\n\n## 📊 Database Schema\n\n\u003ctable\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003eEntity Relationship Diagram\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n```\n┌──────────────┐       ┌──────────────────┐       ┌──────────────┐\n│    Tenant     │──1:N──│ TenantMembership │──N:1──│     User     │\n│              │       │                  │       │              │\n│  tenant_id   │       │  id              │       │  user_id     │\n│  name        │       │  user_id (FK)    │       │  username    │\n│  slug        │       │  tenant_id (FK)  │       │  email       │\n│  business_   │       │  role            │       │  password_   │\n│    type      │       │  is_default      │       │    hash      │\n│  status      │       │  invited_by (FK) │       │  is_super-   │\n│  settings    │       │  status          │       │    admin     │\n│  trial_ends_ │       └──────────────────┘       │  is_active   │\n│    at        │                                   │  neon_auth_  │\n│              │       ┌──────────────────┐       │    user_id   │\n│              │──1:1──│  Subscription    │       └──────────────┘\n│              │       │  tenant_id (FK)  │\n└──────┬───────┘       │  stripe_*_id     │\n       │               │  plan · status   │\n       │               └──────────────────┘\n       │\n       ├──1:N──┬────────────────┬───────────────┬───────────────┐\n       │       │                │               │               │\n┌──────┴──┐ ┌──┴──────┐ ┌──────┴────┐ ┌────────┴──┐ ┌─────────┴──┐\n│Customer │ │  Job     │ │ Service   │ │   Part    │ │ Inventory  │\n│         │ │          │ │           │ │           │ │            │\n│customer_│ │ job_id   │ │service_id │ │ part_id   │ │inventory_id│\n│  id     │ │ job_date │ │service_   │ │ part_name │ │ part_id    │\n│first_   │ │ customer │ │  name     │ │ cost      │ │ qty_on_hand│\n│  name   │ │  (FK)    │ │ cost      │ │ sku       │ │ reorder_   │\n│family_  │ │total_cost│ │description│ │description│ │   level    │\n│  name   │ │completed │ │ category  │ │ category  │ │ location   │\n│email    │ │ paid     │ │est_dur_min│ │ supplier  │ │            │\n│phone    │ │assigned_ │ │ is_active │ │ is_active │ │            │\n│         │ │  to (FK) │ │           │ │           │ │            │\n└─────────┘ └────┬─────┘ └─────┬─────┘ └─────┬─────┘ └──────┬─────┘\n                 │             │             │              │\n                 │  ┌──────────┴─┐  ┌───────┴──────┐       │\n                 ├──│ JobService  │  │  JobPart     │──┤    │\n                 │  │ job_id (PK) │  │ job_id (PK)  │  │    │\n                 │  │ service_id  │  │ part_id (PK) │  │    │\n                 │  │  (PK) · qty │  │ qty          │  │    │\n                 │  └─────────────┘  └──────────────┘  │    │\n                 │                                      │    │\n                 └──────────────────────────────────────┘    │\n                                                             │\n                                               ┌─────────────┴──┐\n                                               │ Inventory      │\n                                               │ Transaction    │\n                                               │ transaction_id │\n                                               │ transaction_   │\n                                               │   type         │\n                                               │ quantity       │\n                                               │ performed_by   │\n                                               │ notes          │\n                                               └────────────────┘\n```\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003eTable Details — Core Tables\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n#### `user`\n| Column | Type | Constraints |\n|--------|------|-------------|\n| `user_id` | Integer | PK |\n| `username` | String(50) | UNIQUE, NOT NULL, indexed |\n| `email` | String(320) | UNIQUE, indexed |\n| `password_hash` | String(255) | NOT NULL |\n| `is_superadmin` | Boolean | default: false |\n| `is_active` | Boolean | default: true |\n| `last_login` | DateTime | nullable |\n| `role` | String(20) | indexed (legacy field) |\n| `neon_auth_user_id` | String(255) | UNIQUE, indexed |\n| `created_at` / `updated_at` | DateTime | auto-managed |\n\n#### `tenant`\n| Column | Type | Constraints |\n|--------|------|-------------|\n| `tenant_id` | Integer | PK |\n| `name` | String(100) | NOT NULL |\n| `slug` | String(100) | UNIQUE, NOT NULL, indexed |\n| `business_type` | String(20) | NOT NULL, default: `auto_repair` |\n| `email` | String(320) | nullable |\n| `phone` | String(20) | nullable |\n| `address` | Text | nullable |\n| `logo_url` | String(500) | nullable |\n| `status` | String(20) | NOT NULL, default: `trial` |\n| `settings` | JSON | nullable |\n| `trial_ends_at` | DateTime | nullable |\n| `created_at` / `updated_at` | DateTime | auto-managed |\n\nTypes: `auto_repair` · `parts_seller` · `both` \u0026nbsp; Statuses: `trial` · `active` · `suspended`\n\n#### `tenant_membership`\n| Column | Type | Constraints |\n|--------|------|-------------|\n| `id` | Integer | PK |\n| `user_id` | Integer | FK → user, NOT NULL |\n| `tenant_id` | Integer | FK → tenant, NOT NULL |\n| `role` | String(20) | NOT NULL, default: `viewer` |\n| `is_default` | Boolean | default: false |\n| `invited_by` | Integer | FK → user |\n| `status` | String(20) | NOT NULL, default: `pending` |\n| `created_at` / `updated_at` | DateTime | auto-managed |\n\nUNIQUE: `(user_id, tenant_id)` \u0026nbsp; Roles: `owner` · `admin` · `manager` · `technician` · `parts_clerk` · `viewer`\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003eTable Details — Business Tables\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n#### `customer`\n| Column | Type | Constraints |\n|--------|------|-------------|\n| `customer_id` | Integer | PK |\n| `tenant_id` | Integer | FK → tenant, indexed |\n| `first_name` | String(25) | nullable |\n| `family_name` | String(25) | NOT NULL |\n| `email` | String(320) | NOT NULL |\n| `phone` | String(11) | NOT NULL |\n\nUNIQUE: `(tenant_id, email)`\n\n#### `job`\n| Column | Type | Constraints |\n|--------|------|-------------|\n| `job_id` | Integer | PK |\n| `tenant_id` | Integer | FK → tenant, indexed |\n| `job_date` | Date | NOT NULL |\n| `customer` | Integer | FK → customer (CASCADE), NOT NULL |\n| `total_cost` | Numeric(6,2) | nullable |\n| `completed` | Boolean | default: false |\n| `paid` | Boolean | default: false |\n| `assigned_to` | Integer | FK → user |\n\nComputed: `is_overdue` (hybrid), `status_text`, `days_since_job`\n\n#### `service`\n| Column | Type | Constraints |\n|--------|------|-------------|\n| `service_id` | Integer | PK |\n| `tenant_id` | Integer | FK → tenant, indexed |\n| `service_name` | String(100) | NOT NULL |\n| `cost` | Numeric(5,2) | NOT NULL |\n| `description` | String(500) | nullable |\n| `category` | String(50) | nullable |\n| `estimated_duration_minutes` | Integer | nullable |\n| `is_active` | Boolean | default: true |\n\n#### `part`\n| Column | Type | Constraints |\n|--------|------|-------------|\n| `part_id` | Integer | PK |\n| `tenant_id` | Integer | FK → tenant, indexed |\n| `part_name` | String(100) | NOT NULL |\n| `cost` | Numeric(5,2) | NOT NULL |\n| `sku` | String(50) | nullable |\n| `description` | String(500) | nullable |\n| `category` | String(50) | nullable |\n| `supplier` | String(100) | nullable |\n| `is_active` | Boolean | default: true |\n\nUNIQUE: `(tenant_id, sku)`\n\n#### `job_service` (junction)\n| Column | Type | Constraints |\n|--------|------|-------------|\n| `job_id` | Integer | PK, FK → job (CASCADE) |\n| `service_id` | Integer | PK, FK → service (CASCADE) |\n| `qty` | Integer | NOT NULL, default: 1 |\n\n#### `job_part` (junction)\n| Column | Type | Constraints |\n|--------|------|-------------|\n| `job_id` | Integer | PK, FK → job (CASCADE) |\n| `part_id` | Integer | PK, FK → part (CASCADE) |\n| `qty` | Integer | NOT NULL, default: 1 |\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003eTable Details — Inventory \u0026 Billing\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n#### `inventory`\n| Column | Type | Constraints |\n|--------|------|-------------|\n| `inventory_id` | Integer | PK |\n| `tenant_id` | Integer | FK → tenant, NOT NULL, indexed |\n| `part_id` | Integer | FK → part, NOT NULL |\n| `quantity_on_hand` | Integer | NOT NULL, default: 0 |\n| `reorder_level` | Integer | NOT NULL, default: 0 |\n| `reorder_quantity` | Integer | NOT NULL, default: 0 |\n| `location` | String(100) | nullable |\n| `created_at` / `updated_at` | DateTime | auto-managed |\n\n#### `inventory_transaction`\n| Column | Type | Constraints |\n|--------|------|-------------|\n| `transaction_id` | Integer | PK |\n| `tenant_id` | Integer | FK → tenant, NOT NULL, indexed |\n| `inventory_id` | Integer | FK → inventory, NOT NULL |\n| `transaction_type` | String(20) | NOT NULL |\n| `quantity` | Integer | NOT NULL |\n| `reference_id` | Integer | nullable |\n| `reference_type` | String(50) | nullable |\n| `performed_by` | Integer | FK → user |\n| `notes` | Text | nullable |\n| `created_at` | DateTime | NOT NULL |\n\nTransaction types: `received` · `sold` · `adjusted` · `returned`\n\n#### `subscription`\n| Column | Type | Constraints |\n|--------|------|-------------|\n| `id` | Integer | PK |\n| `tenant_id` | Integer | FK → tenant, UNIQUE, NOT NULL |\n| `stripe_customer_id` | String(255) | nullable |\n| `stripe_subscription_id` | String(255) | nullable |\n| `plan` | String(20) | NOT NULL, default: `free` |\n| `status` | String(20) | NOT NULL, default: `trialing` |\n| `current_period_start` / `current_period_end` | DateTime | nullable |\n| `trial_ends_at` | DateTime | nullable |\n| `created_at` / `updated_at` | DateTime | auto-managed |\n\nPlans: `free` · `starter` · `professional` · `enterprise` \u0026nbsp; Statuses: `trialing` · `active` · `past_due` · `canceled`\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003eAlembic Migration History\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n| Version | Description |\n|---------|-------------|\n| `001` | Create multi-tenant tables (tenant, membership, subscription, inventory, inventory_transaction). Add `tenant_id` and new columns to existing tables. |\n| `002` | Backfill default tenant (tenant_id=1) for existing data. |\n| `003` | Enforce `NOT NULL` constraints on `tenant_id`. Add unique constraints: `(tenant_id, email)` on customer, `(tenant_id, sku)` on part. |\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003cdiv align=\"right\"\u003e\n\n[![][back-to-top]](#readme-top)\n\n\u003c/div\u003e\n\n\u003c!-- ═══════════════════════════════════════════════════════════════════════════\n     SECTION: Getting Started\n     ═══════════════════════════════════════════════════════════════════════════ --\u003e\n\n## 🚀 Getting Started\n\n### Prerequisites\n\n\u003e [!IMPORTANT]\n\u003e Ensure you have the following installed:\n\n- Python 3.9+ ([Download](https://www.python.org/downloads/))\n- Git ([Download](https://git-scm.com/))\n- [Optional] Node.js 18+ (for Neon CLI)\n- [Optional] Google Cloud Console account (for OAuth)\n- [Optional] Stripe account (for billing)\n\n### Quick Installation\n\n**1. Clone Repository**\n\n```bash\ngit clone https://github.com/ChanMeng666/automotive-repair-management-system.git\ncd automotive-repair-management-system\n```\n\n**2. Create Virtual Environment**\n\n```bash\npython -m venv venv\n\n# Windows\nvenv\\Scripts\\activate\n\n# macOS/Linux\nsource venv/bin/activate\n```\n\n**3. Install Dependencies**\n\n```bash\npip install -r requirements.txt\n```\n\n**4. Configure Environment**\n\n```bash\ncp .env.example .env\n# Edit .env with your settings (see Environment Variables below)\n```\n\n**5. Set Up Neon Database** (Optional — app auto-creates tables in dev mode)\n\n```bash\n# Option A: Neon CLI\nnpm install -g neonctl\nneonctl auth\nneonctl projects create --name automotive-repair\nneonctl connection-string PROJECT_ID\n# Copy connection string to DATABASE_URL in .env\n\n# Option B: Neon Console\n# Create project at https://console.neon.tech\n# Copy connection string from Connection Details\n```\n\n**6. Set Up Google OAuth** (Optional for local dev)\n\n1. Go to [Google Cloud Console](https://console.cloud.google.com/apis/credentials)\n2. Create OAuth 2.0 Client ID\n3. Add redirect URI: `http://localhost:5000/auth/google/callback`\n4. Copy Client ID and Secret to `.env`\n\n**7. Run Application**\n\n```bash\npython run.py\n```\n\nOpen [http://localhost:5000](http://localhost:5000)\n\n\u003e [!TIP]\n\u003e In development mode, the app auto-creates all database tables on startup. No manual schema setup required.\n\n### Environment Variables\n\n\u003e [!WARNING]\n\u003e Never commit `.env` files to version control. Use `.env.example` as a template.\n\n| Variable | Required | Default | Description |\n|----------|----------|---------|-------------|\n| `FLASK_ENV` | 🔶 | `development` | `development` / `production` |\n| `SECRET_KEY` | ✅ (prod) | dev default | Flask session encryption key |\n| `DATABASE_URL` | ✅ (prod) | SQLite in-memory | Neon PostgreSQL connection string |\n| `DB_SSLMODE` | 🔶 | `require` | SSL mode: `require` / `prefer` / `disable` |\n| `GOOGLE_CLIENT_ID` | 🔶 | — | Google OAuth Client ID |\n| `GOOGLE_CLIENT_SECRET` | 🔶 | — | Google OAuth Client Secret |\n| `NEON_AUTH_URL` | 🔶 | — | Neon Auth URL for JWT verification |\n| `NEON_AUTH_JWKS_URL` | 🔶 | — | JWKS endpoint URL |\n| `STRIPE_API_KEY` | 🔶 | — | Stripe API key for billing |\n| `HOST` | 🔶 | `127.0.0.1` | Development server host |\n| `PORT` | 🔶 | `5000` | Development server port |\n| `LOG_LEVEL` | 🔶 | `INFO` | Logging level |\n| `LOG_TO_STDOUT` | 🔶 | `false` | Set `true` for cloud deployments |\n\n\u003e ✅ Required \u0026nbsp; 🔶 Optional\n\n\u003e [!TIP]\n\u003e Use `python -c \"import secrets; print(secrets.token_hex(32))\"` to generate a secure `SECRET_KEY`.\n\n\u003cdiv align=\"right\"\u003e\n\n[![][back-to-top]](#readme-top)\n\n\u003c/div\u003e\n\n\u003c!-- ═══════════════════════════════════════════════════════════════════════════\n     SECTION: Deployment\n     ═══════════════════════════════════════════════════════════════════════════ --\u003e\n\n## 🛳 Deployment\n\n\u003e [!IMPORTANT]\n\u003e The application is production-ready with Heroku deployment via Gunicorn. Neon PostgreSQL provides a serverless, auto-scaling database.\n\n### Deploy to Heroku\n\n**1. Create Heroku App**\n\n```bash\nheroku create your-app-name\n```\n\n**2. Configure Environment Variables**\n\n```bash\nheroku config:set SECRET_KEY=$(python -c \"import secrets; print(secrets.token_hex(32))\")\nheroku config:set FLASK_ENV=production\nheroku config:set LOG_TO_STDOUT=true\nheroku config:set DATABASE_URL=\"postgresql://user:pass@ep-xxx.neon.tech/db?sslmode=require\"\nheroku config:set GOOGLE_CLIENT_ID=\"your-client-id.apps.googleusercontent.com\"\nheroku config:set GOOGLE_CLIENT_SECRET=\"your-client-secret\"\n```\n\n**3. Deploy**\n\n```bash\ngit push heroku main\n```\n\n**4. Google OAuth for Production**\n\n1. Go to [Google Cloud Console](https://console.cloud.google.com/apis/credentials)\n2. Add authorized redirect URI: `https://repairos.chanmeng.org/auth/google/callback`\n3. Add authorized JavaScript origin: `https://repairos.chanmeng.org`\n\n### Database Migrations\n\n```bash\n# Migrations are managed via Alembic in migrations/versions/\n# Current versions: 001 (multi-tenant tables) → 002 (backfill) → 003 (constraints)\n```\n\n\u003cdiv align=\"right\"\u003e\n\n[![][back-to-top]](#readme-top)\n\n\u003c/div\u003e\n\n\u003c!-- ═══════════════════════════════════════════════════════════════════════════\n     SECTION: API Reference\n     ═══════════════════════════════════════════════════════════════════════════ --\u003e\n\n## 📖 API Reference\n\n\u003e [!TIP]\n\u003e All routes require authentication unless marked otherwise. Tenant-scoped routes are also available at `/org/\u003ctenant_slug\u003e/...`.\n\n\u003ctable\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003ePublic Routes\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/` | Home page with statistics dashboard |\n| GET/POST | `/login` | Login page and handler |\n| GET | `/logout` | Logout |\n| GET | `/about` | About page |\n| GET | `/help` | Help page |\n| GET | `/customers` | Customer list |\n| GET/POST | `/customers/new` | New customer form / create |\n| GET | `/customers/\u003cid\u003e` | Customer detail |\n| GET/POST | `/customers/\u003cid\u003e/edit` | Edit customer |\n| GET | `/api/search/customers?q=` | Customer search API (JSON) |\n| GET | `/api/customers/\u003cid\u003e` | Customer details API (JSON) |\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003eAuthentication Routes (/auth)\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET/POST | `/auth/google` | Initiate Google OAuth |\n| GET | `/auth/google/callback` | Google OAuth callback |\n| GET | `/auth/callback` | Neon Auth JWT callback |\n| POST | `/auth/neon-callback` | JS client auth notification |\n| GET | `/auth/session` | Current session info (JSON) |\n| POST | `/auth/logout` | API logout |\n| POST | `/auth/verify-token` | Verify JWT token |\n| POST | `/auth/link-account` | Link Neon Auth to local account |\n| GET | `/auth/status` | OAuth provider config status |\n| GET | `/auth/select-tenant` | Tenant selection page |\n| POST | `/auth/switch-tenant` | Switch active tenant |\n| GET/POST | `/auth/register-organization` | Register new organization |\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003eTechnician Routes (/technician)\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/technician/dashboard` | Technician dashboard |\n| GET | `/technician/current-jobs?page=` | Current work orders (paginated) |\n| GET | `/technician/jobs/\u003cid\u003e` | Work order detail |\n| GET | `/technician/jobs/\u003cid\u003e/modify` | Modify work order |\n| POST | `/technician/jobs/\u003cid\u003e/add-service` | Add service to work order |\n| POST | `/technician/jobs/\u003cid\u003e/add-part` | Add part to work order |\n| POST | `/technician/jobs/\u003cid\u003e/complete` | Mark work order complete |\n| GET | `/technician/jobs/new` | New work order form |\n| POST | `/technician/jobs` | Create work order |\n| GET | `/technician/services` | Service catalog |\n| GET | `/technician/parts` | Parts list |\n| GET | `/technician/api/services` | All services (JSON) |\n| GET | `/technician/api/parts` | All parts (JSON) |\n| GET | `/technician/api/jobs/\u003cid\u003e/status` | Work order status (JSON) |\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003eAdministrator Routes (/administrator)\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/administrator/dashboard` | Admin dashboard with charts |\n| GET | `/administrator/customers?page=` | Customer management (paginated) |\n| GET | `/administrator/billing` | Billing management |\n| GET | `/administrator/overdue-bills` | Overdue bills |\n| GET | `/administrator/pay-bills` | Payment processing |\n| POST | `/administrator/customers/\u003cid\u003e/pay` | Pay all customer bills |\n| POST | `/administrator/jobs/\u003cid\u003e/pay` | Pay single bill |\n| GET | `/administrator/reports` | Reports page |\n| GET/POST | `/administrator/settings` | Organization settings |\n| GET | `/administrator/team` | Team member management |\n| POST | `/administrator/team/invite` | Invite team member |\n| GET/POST | `/administrator/services` | Service catalog management |\n| GET/POST | `/administrator/parts` | Parts catalog management |\n| GET | `/administrator/inventory` | Inventory dashboard |\n| POST | `/administrator/inventory/adjust` | Adjust inventory stock |\n| GET | `/administrator/subscription` | Subscription management |\n| GET | `/administrator/api/dashboard/summary` | Dashboard summary (JSON) |\n| GET | `/administrator/api/billing/statistics` | Billing statistics (JSON) |\n| GET | `/administrator/api/customers/\u003cid\u003e/billing-summary` | Billing summary (JSON) |\n| GET | `/administrator/api/export/customers` | Export customer data (JSON) |\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"10000\"\u003e\n\u003cdetails\u003e\n\n\u003csummary\u003e\u0026nbsp;\u0026nbsp;\u003cstrong\u003eBilling \u0026 Onboarding Routes\u003c/strong\u003e\u003c/summary\u003e\u003cbr\u003e\n\n**Billing (`/billing`):**\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/billing/plans` | Subscription plans |\n| POST | `/billing/checkout` | Create Stripe checkout session |\n| GET | `/billing/success` | Checkout success page |\n| POST | `/billing/portal` | Redirect to Stripe billing portal |\n| POST | `/billing/webhook` | Stripe webhook handler (no auth) |\n\n**Onboarding (`/onboarding`):**\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/onboarding/step/\u003cnum\u003e` | Display onboarding step (1-4) |\n| POST | `/onboarding/step/\u003cnum\u003e` | Save onboarding step data |\n| GET | `/onboarding/complete` | Onboarding completion |\n\n\u003c/details\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003cdiv align=\"right\"\u003e\n\n[![][back-to-top]](#readme-top)\n\n\u003c/div\u003e\n\n\u003c!-- ═══════════════════════════════════════════════════════════════════════════\n     SECTION: Development\n     ═══════════════════════════════════════════════════════════════════════════ --\u003e\n\n## ⌨️ Development\n\n### Development Scripts\n\n```bash\n# Development\npython run.py                              # Start dev server (localhost:5000)\n\n# Testing\npytest                                     # Run all tests\npytest --cov=app                           # Run with coverage (70% minimum)\npytest -m unit                             # Run unit tests only\npytest -m integration                      # Run integration tests only\npytest -m security                         # Run security tests only\npytest tests/unit/test_models.py -v        # Single test file\npytest tests/unit/test_auth.py::TestUserModel::test_create_user -v  # Single test\n\n# Code Quality\nblack --line-length 100 .                  # Format code\nisort .                                    # Sort imports\nflake8 app/                                # Lint code\nmypy app/                                  # Type checking\n\n# Production\ngunicorn wsgi:application --bind 0.0.0.0:$PORT --workers 2 --timeout 120\n```\n\n### Testing\n\n\u003e [!TIP]\n\u003e Tests use SQLite in-memory database — no PostgreSQL setup needed.\n\n**Fixtures** (in `tests/conftest.py`):\n- `app` — Session-scoped Flask app with `TestingConfig`\n- `client` — Function-scoped test client\n- `authenticated_session` — Pre-authenticated technician (tenant_id=1)\n- `admin_session` — Pre-authenticated admin/owner (tenant_id=1)\n\n**Test Markers:**\n- `@pytest.mark.unit` — Unit tests\n- `@pytest.mark.integration` — Integration tests\n- `@pytest.mark.security` — Security tests\n- `@pytest.mark.slow` — Slow-running tests\n\n\u003cdiv align=\"right\"\u003e\n\n[![][back-to-top]](#readme-top)\n\n\u003c/div\u003e\n\n\u003c!-- ═══════════════════════════════════════════════════════════════════════════\n     SECTION: Contributing\n     ═══════════════════════════════════════════════════════════════════════════ --\u003e\n\n## 🤝 Contributing\n\nContributions are welcome! Here's how you can help improve this project:\n\n**1. Fork \u0026 Clone:**\n\n```bash\ngit clone https://github.com/ChanMeng666/automotive-repair-management-system.git\ncd automotive-repair-management-system\n```\n\n**2. Create Branch:**\n\n```bash\ngit checkout -b feature/your-feature-name\n```\n\n**3. Make Changes:**\n\n- Follow Black formatting (line-length: 100)\n- Add tests for new features\n- Ensure all tests pass with `pytest`\n- Update documentation as needed\n\n**4. Submit PR:**\n\n- Provide a clear description of changes\n- Reference related issues\n- Ensure CI passes\n\n**Issue Reporting:**\n- 🐛 **Bug Reports**: Include reproduction steps and environment details\n- 💡 **Feature Requests**: Explain the use case and expected behavior\n- 📚 **Documentation**: Help improve our docs\n\n[![][pr-welcome-shield]][pr-welcome-link]\n\n\u003ca href=\"https://github.com/ChanMeng666/automotive-repair-management-system/graphs/contributors\"\u003e\n  \u003cimg src=\"https://contrib.rocks/image?repo=ChanMeng666/automotive-repair-management-system\" /\u003e\n\u003c/a\u003e\n\n\u003cdiv align=\"right\"\u003e\n\n[![][back-to-top]](#readme-top)\n\n\u003c/div\u003e\n\n\u003c!-- ═══════════════════════════════════════════════════════════════════════════\n     SECTION: License\n     ═══════════════════════════════════════════════════════════════════════════ --\u003e\n\n## 📄 License\n\nThis project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.\n\n- ✅ Commercial use allowed\n- ✅ Modification allowed\n- ✅ Distribution allowed\n- ✅ Private use allowed\n\n\u003c!-- ═══════════════════════════════════════════════════════════════════════════\n     SECTION: Author\n     ═══════════════════════════════════════════════════════════════════════════ --\u003e\n\n## 🙋‍♀️ Author\n\n\u003cdiv align=\"center\"\u003e\n  \u003ctable\u003e\n    \u003ctr\u003e\n      \u003ctd align=\"center\"\u003e\n        \u003ca href=\"https://github.com/ChanMeng666\"\u003e\n          \u003cimg src=\"https://github.com/ChanMeng666.png?size=100\" width=\"100px;\" alt=\"Chan Meng\"/\u003e\n          \u003cbr /\u003e\n          \u003csub\u003e\u003cb\u003eChan Meng\u003c/b\u003e\u003c/sub\u003e\n        \u003c/a\u003e\n        \u003cbr /\u003e\n        \u003csmall\u003eCreator \u0026 Lead Developer\u003c/small\u003e\n      \u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/table\u003e\n\u003c/div\u003e\n\n- \u003cimg src=\"https://cdn.simpleicons.org/internetexplorer/0078D4\" width=\"16\" height=\"16\"\u003e Website: [chanmeng.live](https://chanmeng.live)\n- \u003cimg src=\"https://cdn.simpleicons.org/github/181717\" width=\"16\" height=\"16\"\u003e GitHub: [@ChanMeng666](https://github.com/ChanMeng666)\n- \u003cimg src=\"https://cdn.simpleicons.org/linkedin/0A66C2\" width=\"16\" height=\"16\"\u003e LinkedIn: [chanmeng666](https://www.linkedin.com/in/chanmeng666/)\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n**Built with Flask, SQLAlchemy, Neon PostgreSQL, Google OAuth, Stripe, and Bootstrap**\n\n[Report Bug][github-issues-link] · [Request Feature][github-issues-link]\n\n\u003cbr/\u003e\n\n\u003cimg src=\"https://img.shields.io/github/stars/ChanMeng666/automotive-repair-management-system?style=social\" alt=\"GitHub stars\"\u003e\n\u003cimg src=\"https://img.shields.io/github/forks/ChanMeng666/automotive-repair-management-system?style=social\" alt=\"GitHub forks\"\u003e\n\n\u003c/div\u003e\n\n---\n\n\u003c!-- ═══════════════════════════════════════════════════════════════════════════\n     LINK DEFINITIONS\n     ═══════════════════════════════════════════════════════════════════════════ --\u003e\n\n[back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square\n\n\u003c!-- Project Links --\u003e\n[project-link]: https://repairos.chanmeng.org\n[github-issues-link]: https://github.com/ChanMeng666/automotive-repair-management-system/issues\n[pr-welcome-link]: https://github.com/ChanMeng666/automotive-repair-management-system/pulls\n[license-link]: ./LICENSE\n\n\u003c!-- External Links --\u003e\n[python-link]: https://python.org\n[flask-link]: https://flask.palletsprojects.com\n[sqlalchemy-link]: https://www.sqlalchemy.org\n[postgresql-link]: https://neon.tech\n[stripe-link]: https://stripe.com\n[heroku-link]: https://heroku.com\n[bootstrap-link]: https://getbootstrap.com\n\n\u003c!-- Shield Badges --\u003e\n[python-shield]: https://img.shields.io/badge/Python-3.9+-3776AB?style=for-the-badge\u0026logo=python\u0026logoColor=white\n[flask-shield]: https://img.shields.io/badge/Flask-3.1.3-000000?style=for-the-badge\u0026logo=flask\u0026logoColor=white\n[sqlalchemy-shield]: https://img.shields.io/badge/SQLAlchemy-2.0-D71F00?style=for-the-badge\u0026logo=sqlalchemy\u0026logoColor=white\n[postgresql-shield]: https://img.shields.io/badge/PostgreSQL-Neon-4169E1?style=for-the-badge\u0026logo=postgresql\u0026logoColor=white\n[stripe-shield]: https://img.shields.io/badge/Stripe-Billing-635BFF?style=for-the-badge\u0026logo=stripe\u0026logoColor=white\n[heroku-shield]: https://img.shields.io/badge/Heroku-Deployed-430098?style=for-the-badge\u0026logo=heroku\u0026logoColor=white\n[bootstrap-shield]: https://img.shields.io/badge/Bootstrap-5.3-7952B3?style=for-the-badge\u0026logo=bootstrap\u0026logoColor=white\n[license-shield]: https://img.shields.io/badge/License-MIT-green?style=for-the-badge\n[pr-welcome-shield]: https://img.shields.io/badge/🤝_PRs_welcome-%E2%86%92-ffcb47?labelColor=black\u0026style=for-the-badge\n[demo-shield-badge]: https://img.shields.io/badge/LIVE%20DEMO-ONLINE-55b467?labelColor=black\u0026logo=heroku\u0026style=for-the-badge\n\n\u003c!-- Social Share Links --\u003e\n[share-x-link]: https://x.com/intent/tweet?hashtags=flask,saas,opensource\u0026text=Check%20out%20this%20multi-tenant%20automotive%20repair%20management%20platform\u0026url=https%3A%2F%2Fgithub.com%2FChanMeng666%2Fautomotive-repair-management-system\n[share-telegram-link]: https://t.me/share/url?text=Multi-tenant%20automotive%20repair%20management%20SaaS%20platform\u0026url=https%3A%2F%2Fgithub.com%2FChanMeng666%2Fautomotive-repair-management-system\n[share-reddit-link]: https://www.reddit.com/submit?title=Multi-Tenant%20Automotive%20Repair%20Management%20Platform\u0026url=https%3A%2F%2Fgithub.com%2FChanMeng666%2Fautomotive-repair-management-system\n[share-linkedin-link]: https://linkedin.com/sharing/share-offsite/?url=https://github.com/ChanMeng666/automotive-repair-management-system\n\n[share-x-shield]: https://img.shields.io/badge/-share%20on%20x-black?labelColor=black\u0026logo=x\u0026logoColor=white\u0026style=flat-square\n[share-telegram-shield]: https://img.shields.io/badge/-share%20on%20telegram-black?labelColor=black\u0026logo=telegram\u0026logoColor=white\u0026style=flat-square\n[share-reddit-shield]: https://img.shields.io/badge/-share%20on%20reddit-black?labelColor=black\u0026logo=reddit\u0026logoColor=white\u0026style=flat-square\n[share-linkedin-shield]: https://img.shields.io/badge/-share%20on%20linkedin-black?labelColor=black\u0026logo=linkedin\u0026logoColor=white\u0026style=flat-square\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchanmeng666%2Fautomotive-repair-management-system","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchanmeng666%2Fautomotive-repair-management-system","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchanmeng666%2Fautomotive-repair-management-system/lists"}