# Purchase Order API v2.0

Production-grade REST API for exposing purchase order data from the GCX ERP system.

## 🚀 Features

### Core Features
- **RESTful API Design**: Clean, intuitive endpoints following REST best practices
- **API Key Authentication**: Secure access with API key validation
- **Pagination**: Efficient data retrieval with pagination support
- **Filtering & Search**: Advanced query capabilities with fuzzy matching
- **Comprehensive Data**: Full purchase order details including items, payments, and supplier info

### Infrastructure (v2.0)
- **Database-Backed Caching**: L1 in-memory + L2 MySQL InnoDB cache for sub-millisecond responses
- **Token Bucket Rate Limiting**: Smart rate limiting with automatic token refill
- **Structured Logging**: All requests logged to database with request tracing
- **Request Tracking**: Unique `X-Request-ID` header on all responses
- **Connection Pooling**: Optimized database connections for high throughput
- **Horizontal Scaling Ready**: Cluster management and distributed locking support
- **Async Job Queue**: Background processing for heavy operations

---

## 📋 Table of Contents

1. [Quick Start](#quick-start)
2. [Configuration](#configuration)
3. [API Endpoints](#api-endpoints)
4. [Authentication](#authentication)
5. [Query Parameters](#query-parameters)
6. [Response Format](#response-format)
7. [Integration Examples](#integration-examples)
8. [Infrastructure](#infrastructure)
9. [Error Handling](#error-handling)
10. [Project Structure](#project-structure)

---

## ⚡ Quick Start

### 1. Clone & Configure

```bash
# Copy environment file
cp .env.example .env

# Edit with your credentials
nano .env
```

### 2. Environment Variables

```env
# Database Configuration
DB_HOST=localhost
DB_DATABASE=sserp_gcxops
DB_USERNAME=root
DB_PASSWORD=your_password

# API Security
API_KEY=gcx-po-api-dev-key-2024-secure-token

# Optional: Rate Limiting (defaults shown)
RATE_LIMIT_REQUESTS=100
RATE_LIMIT_WINDOW=60
```

### 3. Run Database Migrations

```sql
-- Run the infrastructure migration
SOURCE database/migrations/create_infrastructure_tables.sql;
```

### 4. Test the API

```bash
# Health check (no auth required)
curl http://poapi.io/api/health

# List purchase orders (auth required)
curl -H "X-API-Key: gcx-po-api-dev-key-2024-secure-token" \
     http://poapi.io/api/purchase-orders
```

---

## 🔧 Configuration

### Web Server Setup

**For WAMP (Windows)**:
```
API available at: http://localhost/poapi/public/api/
```

**For Apache Virtual Host**:
```apache
<VirtualHost *:80>
    ServerName poapi.io
    DocumentRoot "C:/wamp64/www/poapi/public"
    
    <Directory "C:/wamp64/www/poapi/public">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
```

Add to hosts file:
```
127.0.0.1 poapi.io
```

---

## 📡 API Endpoints

### Public Endpoints (No Authentication)

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/health` | Health check with component status |
| GET | `/api/` | API information and available endpoints |

### Purchase Orders

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/purchase-orders` | List all orders with pagination & filters |
| GET | `/api/purchase-orders/{id}` | Get single order with full details |
| GET | `/api/purchase-orders/{id}/items` | Get order line items |
| GET | `/api/purchase-orders/{id}/payments` | Get payment history |
| GET | `/api/purchase-orders/statistics` | Get summary statistics |
| GET | `/api/purchase-orders/recent` | Get recent orders |
| GET | `/api/purchase-orders/search?q={query}` | Full-text search |

### Suppliers

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/suppliers` | List all suppliers |
| GET | `/api/suppliers/{id}` | Get supplier with statistics |
| GET | `/api/suppliers/{id}/orders` | Get supplier's orders |
| GET | `/api/suppliers/search?q={query}` | Search suppliers |
| GET | `/api/suppliers/top` | Top suppliers by value |
| GET | `/api/suppliers/lookup?q={name}` | **Fuzzy lookup** with suggestions |
| GET | `/api/suppliers/suggest?q={name}` | **Auto-suggest** with confidence scoring |
| GET | `/api/suppliers/sync?since={timestamp}` | **Incremental sync** - get only new/updated |
| GET | `/api/suppliers/check-duplicate?name={name}` | Check for duplicates |

### Cache Management (Admin)

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/cache/stats` | View cache statistics |
| POST | `/api/cache/clear` | Clear all cache |

---

## 🔐 Authentication

### Header Authentication (Recommended)

```http
X-API-Key: gcx-po-api-dev-key-2024-secure-token
```

### Bearer Token (Alternative)

```http
Authorization: Bearer gcx-po-api-dev-key-2024-secure-token
```

### Response Headers

Every response includes:
```http
X-Request-ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
X-RateLimit-Remaining: 95
```

---

## 🔍 Query Parameters

### Pagination

| Parameter | Default | Max | Description |
|-----------|---------|-----|-------------|
| `page` | 1 | - | Page number |
| `per_page` / `limit` | 20 | 100 | Items per page |

### Purchase Order Filters

| Parameter | Description | Example |
|-----------|-------------|---------|
| `order_no` | Exact order number | `PO-002424` |
| `supplier_id` | Filter by supplier ID | `00243` |
| `status` | Status (0=Closed, 1=Active) | `1` |
| `date_from` | Orders from date | `2024-01-01` |
| `date_to` | Orders to date | `2024-12-31` |
| `month` | Filter by month (1-12) | `10` |
| `year` | Filter by year | `2024` |
| `min_value` | Minimum order value | `10000` |
| `max_value` | Maximum order value | `1000000` |
| `location` | Filter by location | `Kumasi` |

### Supplier Lookup Filters

| Parameter | Description | Example |
|-----------|-------------|---------|
| `q` | Search query (fuzzy match) | `gem light` |
| `confidence` | Minimum confidence (0-100) | `80` |
| `limit` | Max suggestions | `5` |

---

## 📦 Response Format

### Success Response

```json
{
    "success": true,
    "message": "Purchase orders retrieved successfully",
    "data": [...],
    "pagination": {
        "total": 150,
        "per_page": 20,
        "current_page": 1,
        "total_pages": 8,
        "has_more": true
    },
    "timestamp": "2025-12-04T14:30:00+00:00"
}
```

### Health Check Response

```json
{
    "success": true,
    "message": "API is running",
    "data": {
        "status": "healthy",
        "version": "2.0.0",
        "timestamp": "2025-12-04T14:30:00+00:00",
        "timezone": "Africa/Accra",
        "request_id": "a1b2c3d4-e5f67890",
        "components": {
            "database": "healthy",
            "cache": "enabled",
            "rate_limiter": "enabled"
        },
        "cache_stats": {
            "hit_rate": "85.5%",
            "memory_usage": "127 items"
        }
    }
}
```

### Error Response

```json
{
    "success": false,
    "message": "Rate limit exceeded. Please slow down.",
    "error_code": 429,
    "timestamp": "2025-12-04T14:30:00+00:00"
}
```

---

## 💡 Integration Examples

### JavaScript (Fetch API)

```javascript
const API_BASE = 'http://poapi.io/api';
const API_KEY = 'gcx-po-api-dev-key-2024-secure-token';

// Fetch purchase orders with filters
async function getPurchaseOrders(filters = {}) {
    const params = new URLSearchParams({
        page: 1,
        limit: 20,
        ...filters
    });
    
    const response = await fetch(`${API_BASE}/purchase-orders?${params}`, {
        headers: {
            'X-API-Key': API_KEY,
            'Accept': 'application/json'
        }
    });
    
    if (!response.ok) {
        throw new Error(`API error: ${response.status}`);
    }
    
    return response.json();
}

// Example: Get October 2024 orders
const orders = await getPurchaseOrders({ month: 10, year: 2024 });
console.log(`Found ${orders.pagination.total} orders`);
```

### JavaScript (with Caching & Error Handling)

```javascript
class PurchaseOrderAPI {
    constructor(apiKey, baseUrl = 'http://poapi.io/api') {
        this.apiKey = apiKey;
        this.baseUrl = baseUrl;
        this.cache = new Map();
        this.cacheTTL = 5 * 60 * 1000; // 5 minutes
    }
    
    async request(endpoint, params = {}) {
        const cacheKey = `${endpoint}?${new URLSearchParams(params)}`;
        
        // Check cache
        const cached = this.cache.get(cacheKey);
        if (cached && Date.now() < cached.expires) {
            return cached.data;
        }
        
        const url = `${this.baseUrl}${endpoint}?${new URLSearchParams(params)}`;
        
        const response = await fetch(url, {
            headers: {
                'X-API-Key': this.apiKey,
                'Accept': 'application/json'
            }
        });
        
        // Handle rate limiting
        if (response.status === 429) {
            const retryAfter = response.headers.get('Retry-After') || 60;
            throw new Error(`Rate limited. Retry after ${retryAfter}s`);
        }
        
        if (!response.ok) {
            const error = await response.json();
            throw new Error(error.message || 'API request failed');
        }
        
        const data = await response.json();
        
        // Cache successful responses
        this.cache.set(cacheKey, {
            data,
            expires: Date.now() + this.cacheTTL
        });
        
        return data;
    }
    
    // Purchase Orders
    getOrders(filters = {}) {
        return this.request('/purchase-orders', filters);
    }
    
    getOrder(id) {
        return this.request(`/purchase-orders/${id}`);
    }
    
    getStatistics(filters = {}) {
        return this.request('/purchase-orders/statistics', filters);
    }
    
    searchOrders(query) {
        return this.request('/purchase-orders/search', { q: query });
    }
    
    // Suppliers
    getSuppliers(filters = {}) {
        return this.request('/suppliers', filters);
    }
    
    lookupSupplier(name) {
        return this.request('/suppliers/lookup', { q: name });
    }
    
    suggestSupplier(name) {
        return this.request('/suppliers/suggest', { q: name });
    }
    
    syncSuppliers(since) {
        return this.request('/suppliers/sync', { since });
    }
}

// Usage
const api = new PurchaseOrderAPI('gcx-po-api-dev-key-2024-secure-token');

// Get orders by month/year
const orders = await api.getOrders({ month: 10, year: 2024, status: 1 });

// Fuzzy supplier lookup
const supplier = await api.lookupSupplier('gem light');
if (supplier.data.best_match) {
    console.log(`Found: ${supplier.data.best_match.company_name}`);
}

// Incremental sync
const lastSync = localStorage.getItem('lastSupplierSync');
const updates = await api.syncSuppliers(lastSync);
localStorage.setItem('lastSupplierSync', new Date().toISOString());
```

### PHP Integration

```php
<?php
class PurchaseOrderAPI {
    private string $apiKey;
    private string $baseUrl;
    
    public function __construct(string $apiKey, string $baseUrl = 'http://poapi.io/api') {
        $this->apiKey = $apiKey;
        $this->baseUrl = $baseUrl;
    }
    
    public function request(string $endpoint, array $params = []): array {
        $url = $this->baseUrl . $endpoint;
        if ($params) {
            $url .= '?' . http_build_query($params);
        }
        
        $context = stream_context_create([
            'http' => [
                'method' => 'GET',
                'header' => "X-API-Key: {$this->apiKey}\r\nAccept: application/json\r\n",
                'timeout' => 30
            ]
        ]);
        
        $response = file_get_contents($url, false, $context);
        
        if ($response === false) {
            throw new Exception('API request failed');
        }
        
        return json_decode($response, true);
    }
    
    public function getOrders(array $filters = []): array {
        return $this->request('/purchase-orders', $filters);
    }
    
    public function getOrder(string $id): array {
        return $this->request("/purchase-orders/{$id}");
    }
    
    public function lookupSupplier(string $name): array {
        return $this->request('/suppliers/lookup', ['q' => $name]);
    }
}

// Usage
$api = new PurchaseOrderAPI('gcx-po-api-dev-key-2024-secure-token');

// Get October 2024 orders
$orders = $api->getOrders(['month' => 10, 'year' => 2024]);
echo "Total orders: " . $orders['pagination']['total'];

// Fuzzy supplier lookup
$result = $api->lookupSupplier('gem light');
if ($result['data']['best_match']) {
    echo "Found: " . $result['data']['best_match']['company_name'];
}
```

### cURL Examples

```bash
# List orders with filters
curl -s "http://poapi.io/api/purchase-orders?month=10&year=2024&limit=10" \
  -H "X-API-Key: gcx-po-api-dev-key-2024-secure-token"

# Get specific order
curl -s "http://poapi.io/api/purchase-orders/PO-002424" \
  -H "X-API-Key: gcx-po-api-dev-key-2024-secure-token"

# Fuzzy supplier lookup
curl -s "http://poapi.io/api/suppliers/lookup?q=gem%20light" \
  -H "X-API-Key: gcx-po-api-dev-key-2024-secure-token"

# Auto-suggest supplier (with confidence)
curl -s "http://poapi.io/api/suppliers/suggest?q=gem%20light" \
  -H "X-API-Key: gcx-po-api-dev-key-2024-secure-token"

# Incremental sync (get suppliers updated since timestamp)
curl -s "http://poapi.io/api/suppliers/sync?since=2024-01-01T00:00:00Z" \
  -H "X-API-Key: gcx-po-api-dev-key-2024-secure-token"

# Check for duplicate suppliers
curl -s "http://poapi.io/api/suppliers/check-duplicate?name=GEM%20LIGHT" \
  -H "X-API-Key: gcx-po-api-dev-key-2024-secure-token"

# Get statistics
curl -s "http://poapi.io/api/purchase-orders/statistics?year=2024" \
  -H "X-API-Key: gcx-po-api-dev-key-2024-secure-token"
```

---

## 🏗️ Infrastructure

### Database-Backed Cache

The API uses a two-tier caching system:
- **L1 (Memory)**: In-process PHP array cache (instant)
- **L2 (Database)**: MySQL InnoDB table (millisecond access)

```sql
-- Cache table created automatically
CREATE TABLE cache_store (
    cache_key VARCHAR(255) PRIMARY KEY,
    cache_value MEDIUMBLOB NOT NULL,
    expires_at INT UNSIGNED NOT NULL,
    hits INT UNSIGNED DEFAULT 0
);
```

### Token Bucket Rate Limiting

Uses the token bucket algorithm for fair rate limiting:
- **Capacity**: 100 tokens (requests)
- **Refill Rate**: 10 tokens/second
- **Persistence**: MySQL for multi-server consistency

### Structured Logging

All requests are logged to the `logs` table:
- Request/Response times
- Error tracking with stack traces
- Request tracing via `X-Request-ID`

### Running Migrations

```bash
# Connect to MySQL
mysql -u root -p sserp_gcxops

# Run migrations
SOURCE database/migrations/create_infrastructure_tables.sql;
```

---

## ❌ Error Handling

### HTTP Status Codes

| Code | Description | Action |
|------|-------------|--------|
| 200 | Success | Process response |
| 400 | Bad Request | Check parameters |
| 401 | Unauthorized | Add/check API key |
| 403 | Forbidden | Invalid API key |
| 404 | Not Found | Resource doesn't exist |
| 429 | Rate Limited | Wait and retry |
| 500 | Server Error | Report issue |

### Rate Limit Response

```json
{
    "success": false,
    "message": "Rate limit exceeded. Please slow down.",
    "error_code": 429
}
```

**Headers:**
```http
X-RateLimit-Remaining: 0
Retry-After: 6
```

### Handling Rate Limits (JavaScript)

```javascript
async function fetchWithRetry(url, options, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
        const response = await fetch(url, options);
        
        if (response.status === 429) {
            const retryAfter = parseInt(response.headers.get('Retry-After') || '10');
            console.log(`Rate limited. Waiting ${retryAfter}s...`);
            await new Promise(r => setTimeout(r, retryAfter * 1000));
            continue;
        }
        
        return response;
    }
    throw new Error('Max retries exceeded');
}
```

---

## 📁 Project Structure

```
poapi/
├── config/
│   ├── database.php          # Database configuration
│   └── env.php               # Environment loader
│
├── database/
│   └── migrations/
│       └── create_infrastructure_tables.sql
│
├── public/
│   ├── index.php             # Entry point (v2.0)
│   ├── index.html            # Frontend dashboard
│   ├── po-print.html         # Print/PDF template
│   └── .htaccess             # URL rewriting
│
├── src/
│   ├── Controllers/
│   │   ├── PurchaseOrderController.php
│   │   └── SupplierController.php
│   │
│   ├── Core/
│   │   ├── Application.php   # Bootstrap & DI container
│   │   ├── Cache.php         # Database-backed cache
│   │   ├── ClusterManager.php# Horizontal scaling
│   │   ├── ConnectionPool.php# Connection pooling
│   │   ├── Gateway.php       # API Gateway
│   │   ├── JobQueue.php      # Async job processing
│   │   ├── LoadBalancer.php  # Traffic distribution
│   │   ├── Logger.php        # Structured logging
│   │   ├── RateLimiter.php   # Token bucket rate limiting
│   │   ├── Router.php        # Request routing
│   │   ├── ServiceRegistry.php # Service discovery
│   │   └── Validator.php     # Input validation
│   │
│   ├── Middleware/
│   │   ├── ApiKeyAuth.php    # API key authentication
│   │   ├── CorsMiddleware.php# CORS handling
│   │   └── RateLimiter.php   # Legacy rate limiter
│   │
│   ├── Repositories/
│   │   ├── PurchaseOrderRepository.php
│   │   └── SupplierRepository.php
│   │
│   ├── Services/             # Microservices
│   │   ├── BaseService.php   # Base service class
│   │   ├── PurchaseOrderService.php
│   │   └── SupplierService.php
│   │
│   └── Utils/
│       └── Response.php      # JSON response helper
│
├── storage/
│   └── rate_limits/          # Legacy file storage
│
├── .env.example              # Environment template
├── .htaccess                 # Root URL rewriting
└── README.md                 # This file
```

---

## 🌐 Microservices Architecture (v2.0)

### Overview

The API supports a microservices architecture with:
- **API Gateway**: Central entry point for all requests
- **Service Registry**: Dynamic service discovery
- **Load Balancer**: Traffic distribution across nodes
- **Circuit Breaker**: Fault tolerance and resilience

### Components

#### Gateway (`src/Core/Gateway.php`)
Routes requests to appropriate microservices with:
- Request authentication
- Rate limiting
- Response aggregation
- Error handling

#### Service Registry (`src/Core/ServiceRegistry.php`)
Manages service discovery:
- Service registration/deregistration
- Health monitoring
- Version tracking
- Dependency management

#### Load Balancer (`src/Core/LoadBalancer.php`)
Distributes traffic across service nodes:
- **Algorithms**: Round Robin, Weighted, Least Connections, Random, IP Hash
- **Health Checks**: Automatic unhealthy node removal
- **Circuit Breaker**: Automatic failure detection

### Available Services

| Service | Base Path | Description |
|---------|-----------|-------------|
| `purchase-orders` | `/purchase-orders` | PO management |
| `suppliers` | `/suppliers` | Supplier management |

### Gateway Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/gateway/health` | Gateway health status |
| GET | `/gateway/metrics` | Performance metrics |
| POST | `/gateway/register` | Register a service |
| POST | `/gateway/nodes` | Add a service node |

### Registering a Service

```bash
curl -X POST "http://poapi.io/api/gateway/register" \
  -H "X-API-Key: gcx-po-api-dev-key-2024-secure-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-service",
    "base_path": "/my-service",
    "description": "My custom service",
    "options": {
      "rate_limit": 100,
      "timeout_ms": 30000
    }
  }'
```

### Adding a Service Node

```bash
curl -X POST "http://poapi.io/api/gateway/nodes" \
  -H "X-API-Key: gcx-po-api-dev-key-2024-secure-token" \
  -H "Content-Type: application/json" \
  -d '{
    "service": "purchase-orders",
    "host": "node2.example.com",
    "port": 80,
    "weight": 100
  }'
```

### Database Tables

Run the migration to create microservices tables:

```bash
php database/migrations/create_microservices_tables.php
```

Creates:
- `service_registry` - Service definitions
- `service_nodes` - Load balancer nodes
- `circuit_breaker` - Fault tolerance state
- `gateway_requests` - Request analytics
- `service_config` - Configuration store

---

## 🔒 Security Recommendations

1. **Use HTTPS** in production
2. **Generate strong API keys** (minimum 32 characters)
3. **Restrict CORS origins** in production:
   ```php
   // In CorsMiddleware.php
   $allowedOrigins = ['https://yourdomain.com'];
   ```
4. **Rotate API keys** periodically
5. **Monitor logs** for suspicious activity
6. **Enable rate limiting** with appropriate values
7. **Hide error details** in production (`APP_DEBUG=false`)

---

## 📊 Performance Tips

1. **Use pagination** - Don't fetch all records at once
2. **Filter early** - Use query parameters to reduce data transfer
3. **Cache responses** - Implement client-side caching
4. **Use incremental sync** - `/suppliers/sync?since=timestamp`
5. **Batch requests** - Combine related requests when possible

---

## 📞 Support

For issues or questions, contact the GCX Development Team.

---

## 📄 License

Proprietary - GCX Ghana Ltd. © 2025
