ManufactureERPTechnical Docs

Complete technical documentation for developers building on ManufactureERP.

🎯 Purpose

ManufactureERP is a multi-tenant SaaS platform providing modular ERP functionality for manufacturing companies. This documentation covers architecture, development, and deployment.

System Overview

ManufactureERP is built with the following principles:

  • Multi-tenancy: Each organization has isolated data storage
  • Modularity: Features are organized into independent, subscribable modules
  • JWT Authentication: Stateless, client-side authentication
  • Serverless: Runs on Google Cloud Run with auto-scaling
  • Pay-per-module: Organizations subscribe only to modules they need

Key Features

Feature Description
Project Management Gantt charts, task tracking, team collaboration
Press Production Operation norms, efficiency tracking, daily planning
Maintenance Tool inventory, PM schedules, MTBF analytics
Future Modules Inventory, Quality Control, Costing & Analytics

Architecture

System Architecture Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              CLIENT (Browser)                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚  β”‚HTML/CSS/JSβ”‚ β”‚LocalStorageβ”‚ β”‚API Clientβ”‚      β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚ HTTPS (JWT in Authorization)
                    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         BACKEND (Flask on Cloud Run)             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚         app.py (Factory)                β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                    β”‚                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”‚
β”‚  β”‚   Blueprints    β”‚   Services     β”‚           β”‚
β”‚  β”‚   β€’ auth        β”‚   β€’ auth       β”‚           β”‚
β”‚  β”‚   β€’ org         β”‚   β€’ org        β”‚           β”‚
β”‚  β”‚   β€’ frontend    β”‚   β€’ storage    β”‚           β”‚
β”‚  β”‚                 β”‚                 β”‚           β”‚
β”‚  β”‚   Modules       β”‚   Decorators   β”‚           β”‚
β”‚  β”‚   β€’ projects    β”‚   @login       β”‚           β”‚
β”‚  β”‚   β€’ press       β”‚   @module      β”‚           β”‚
β”‚  β”‚   β€’ maintenance β”‚   @admin       β”‚           β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚ Google Cloud APIs
                    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    STORAGE (Google Cloud Storage)               β”‚
β”‚                                                  β”‚
β”‚  manufacture-erp-data-prod/                     β”‚
β”‚    └── organizations/                            β”‚
β”‚        └── {org-slug}/                           β”‚
β”‚            β”œβ”€β”€ organization.json                 β”‚
β”‚            β”œβ”€β”€ users.json                        β”‚
β”‚            β”œβ”€β”€ projects/                         β”‚
β”‚            β”œβ”€β”€ press_production/                 β”‚
β”‚            └── maintenance/                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    

Technology Stack

Backend

  • Framework: Flask 3.0
  • Language: Python 3.11
  • Authentication: PyJWT
  • Storage: Google Cloud Storage
  • Hosting: Google Cloud Run (Serverless)

Frontend

  • JavaScript: Vanilla JS (No frameworks)
  • CSS: Custom stylesheets
  • Charts: Chart.js
  • Icons: Lucide Icons

Core Components

Application Factory (app.py)

def create_app():
    """
    Creates Flask app with:
    - Configuration loading
    - Blueprint registration
    - Error handlers
    - Logging setup
    """
    app = Flask(__name__)
    app.config.from_object(get_config())
    
    register_blueprints(app)
    setup_logging(app)
    
    return app

Configuration (config.py)

class Config:
    # JWT Settings
    JWT_SECRET_KEY = os.getenv('JWT_SECRET_KEY')
    JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=24)
    
    # GCS Settings
    GCS_BUCKET_NAME = os.getenv('GCS_BUCKET_NAME')
    
    # Module Pricing
    MODULE_PRICING = {
        'project_management': {
            'name': 'Project Management',
            'price': 3999,
            'trial_days': 30
        }
    }

Decorators (core/decorators.py)

@login_required

def login_required(f):
    """Validates JWT token and loads user + org"""
    @wraps(f)
    def decorated(*args, **kwargs):
        # 1. Extract token from Authorization header
        # 2. Verify JWT
        # 3. Load organization
        # 4. Load user
        # 5. Store in flask.g
        return f(*args, **kwargs)
    return decorated

@module_required

def module_required(module_name):
    """Checks if org has active subscription"""
    def decorator(f):
        @wraps(f)
        def decorated(*args, **kwargs):
            org = g.current_organization
            
            if module_name not in org.active_modules:
                # Check trial period
                if module_name in org.trial_modules:
                    # Allow if in trial
                    pass
                else:
                    return jsonify({
                        'error': 'Module not active'
                    }), 403
            
            return f(*args, **kwargs)
        return decorated
    return decorator
⚠️ Critical

Always use @module_required after @login_required on ALL module endpoints. Missing this decorator allows unauthorized access.

Module System Deep Dive

What is a Module?

A module is a self-contained feature set that can be:

  • Independently subscribed to
  • Enabled/disabled per organization
  • Developed without affecting other modules

Module Structure

modules/
└── project_management/
    β”œβ”€β”€ __init__.py           # Exports blueprint
    β”œβ”€β”€ routes.py             # API endpoints
    β”œβ”€β”€ services.py           # Business logic
    β”œβ”€β”€ models.py             # Data models
    └── gantt_service.py      # Specialized services

How to Add New Modules

βœ… Module Checklist
  1. Create module directory structure
  2. Define data models
  3. Create routes with @module_required
  4. Register blueprint in app.py
  5. Add to MODULE_PRICING in config.py
  6. Create frontend templates
  7. Add to sidebar navigation
  8. Deploy to Cloud Run

Step 1: Create Module Structure

mkdir -p modules/inventory
touch modules/inventory/__init__.py
touch modules/inventory/routes.py
touch modules/inventory/services.py
touch modules/inventory/models.py

Step 2: Define Routes

from flask import Blueprint
from core.decorators import login_required, module_required

inventory_bp = Blueprint('inventory', __name__, 
                        url_prefix='/api/inventory')

@inventory_bp.route('/items', methods=['GET'])
@login_required
@module_required('inventory')  # ← CRITICAL
def get_items():
    # Implementation
    pass

Step 3: Register Blueprint

# In app.py
from modules.inventory import inventory_bp

app.register_blueprint(inventory_bp)

Step 4: Add to Config

# In config.py
MODULE_PRICING = {
    'inventory': {
        'name': 'Inventory Management',
        'price': 2499,
        'trial_days': 30
    }
}

Authentication & Authorization

Authentication Flow

1. User submits login (email + password)
   ↓
2. Server verifies credentials
   ↓
3. Server generates JWT tokens
   β€’ Access Token (24 hours)
   β€’ Refresh Token (30 days)
   ↓
4. Client stores tokens in localStorage
   ↓
5. Client includes token in all API requests
   Authorization: Bearer <access_token>
   ↓
6. Server validates token on each request
   β€’ Decodes JWT
   β€’ Loads user + organization
   β€’ Checks module access
                    

JWT Token Structure

{
  "id": "user-uuid",
  "email": "user@company.com",
  "organization_id": "acme-manufacturing",
  "role": "admin",
  "exp": 1730400000
}

Data Storage Architecture

Storage Layout

manufacture-erp-data-prod/
└── organizations/
    └── acme-manufacturing/
        β”œβ”€β”€ organization.json
        β”œβ”€β”€ users.json
        β”œβ”€β”€ subscription.json
        β”œβ”€β”€ projects/
        β”‚   β”œβ”€β”€ projects.json
        β”‚   └── project-123/
        β”‚       β”œβ”€β”€ tasks.json
        β”‚       └── comments.json
        └── press_production/
            β”œβ”€β”€ operations.json
            └── machines.json

Why JSON in GCS?

Advantage Trade-off
βœ… Simple - no schema migrations ❌ No complex queries
βœ… Cost-effective ❌ Not for high-frequency writes
βœ… Scalable per tenant ❌ Must load and filter
βœ… Built-in versioning ❌ No transactions

API Reference

Authentication Endpoints

POST /api/auth/register

Create new organization with owner user.

// Request
{
  "organization_name": "Acme Manufacturing",
  "owner_name": "John Doe",
  "owner_email": "john@acme.com",
  "owner_password": "SecurePass123"
}

// Response
{
  "message": "Organization created successfully",
  "access_token": "...",
  "refresh_token": "..."
}

POST /api/auth/login

Login with email and password.

// Request
{
  "email": "john@acme.com",
  "password": "SecurePass123"
}

// Response
{
  "message": "Login successful",
  "user": {...},
  "organization": {...},
  "access_token": "...",
  "refresh_token": "..."
}

GET /api/auth/me

Get current user information.

// Headers
Authorization: Bearer <access_token>

// Response
{
  "user": {...},
  "organization": {...}
}

Project Management Endpoints

GET /api/projects

// Response
{
  "projects": [
    {
      "id": "uuid",
      "name": "Product Launch",
      "status": "active",
      "progress": 45.5
    }
  ]
}

POST /api/projects

// Request
{
  "name": "New Product Launch",
  "project_manager": "manager@company.com",
  "start_date": "2025-11-01",
  "target_end_date": "2025-12-31"
}

Frontend Architecture

API Client (api.js)

class APIClient {
    constructor() {
        this.baseURL = '/api';
    }
    
    getHeaders() {
        const headers = {
            'Content-Type': 'application/json'
        };
        
        const token = localStorage.getItem('access_token');
        if (token) {
            headers['Authorization'] = `Bearer ${token}`;
        }
        
        return headers;
    }
    
    async get(endpoint) {
        return this.request(endpoint, { method: 'GET' });
    }
    
    async post(endpoint, body) {
        return this.request(endpoint, {
            method: 'POST',
            body: JSON.stringify(body)
        });
    }
}

const api = new APIClient();

Authentication Manager (auth.js)

class AuthManager {
    checkAuthOnProtectedPages() {
        const publicPaths = ['/login', '/register', '/'];
        
        if (publicPaths.includes(window.location.pathname)) {
            return;
        }
        
        const token = localStorage.getItem('access_token');
        if (!token) {
            window.location.href = '/login';
        }
    }
    
    async login(email, password) {
        const response = await api.post('/auth/login', {
            email, password
        });
        
        localStorage.setItem('access_token', response.access_token);
        localStorage.setItem('refresh_token', response.refresh_token);
        
        window.location.href = '/dashboard';
    }
}

const auth = new AuthManager();

Deployment to Cloud Run

Prerequisites

  • Google Cloud account
  • gcloud CLI installed
  • Project created in GCP

Setup Steps

# Login and configure
gcloud auth login
gcloud config set project YOUR_PROJECT_ID

# Enable APIs
gcloud services enable run.googleapis.com
gcloud services enable storage.googleapis.com

# Create GCS bucket
gcloud storage buckets create \
  gs://manufacture-erp-data-prod \
  --location=asia-south1

# Deploy to Cloud Run
gcloud run deploy manufacture-erp \
  --source . \
  --region asia-south1 \
  --allow-unauthenticated \
  --set-env-vars "GCS_BUCKET_NAME=manufacture-erp-data-prod"

Environment Variables

GCS_BUCKET_NAME=manufacture-erp-data-prod
JWT_SECRET_KEY=your-secret-key
FLASK_ENV=production
DEBUG=false

Custom Domain with Cloudflare

  1. Add domain to Cloudflare
  2. Update nameservers at registrar
  3. Add DNS record:
    Type: CNAME
    Name: @
    Target: manufacture-erp-xxx.a.run.app
    Proxy: ON
  4. Set SSL to "Full (strict)"
  5. Done! πŸŽ‰

Troubleshooting

Issue: 403 Forbidden on Module Endpoints

Cause: Missing @module_required decorator

Fix: Add decorator to ALL module endpoints

@press_production_bp.route('/operations', methods=['GET'])
@login_required
@module_required('press_production')  # ← Add this
def get_operations():
    pass

Issue: Token Expired Errors

Cause: Access token expired (24 hours)

Fix: API client auto-refreshes tokens

Issue: GCS Permission Denied

Cause: Service account lacks permissions

gcloud storage buckets add-iam-policy-binding \
  gs://manufacture-erp-data-prod \
  --member="serviceAccount:${SERVICE_ACCOUNT}" \
  --role="roles/storage.objectAdmin"

πŸ“ž Need Help?

For questions or support, contact the development team or visit our GitHub repository.