Skip to main content

Overview

The Order Management endpoint retrieves all orders for a specific tenant. This endpoint is primarily used in the tenant dashboard to display order history and analytics.
Authentication Required: This endpoint requires user authentication via Laravel’s auth middleware.
Plan Restriction: Only tenants with the cat-anual plan have access to order data. Other plans will receive an empty order list.

Get Tenant Orders

Retrieve all orders for a tenant, sorted by date (most recent first).
tenantId
integer
required
The unique tenant identifier

Response

Returns an HTML view (dashboard.components.orders-section) with the following data:
orders
array
Array of order objects sorted by date (descending)
tenant
object
Tenant model with plan relationship loaded
isPlanAnual
boolean
Whether the tenant has the cat-anual plan
curl -X GET https://yourdomain.com/tenant/123/orders \
  -H "Cookie: synticorex_session=your_session_token"

Order Data Structure

Each order in the orders array contains the following fields:
id
string
Unique order identifier (format: SC-XXXXXX)
tenant_id
integer
Tenant owner ID
date
string
ISO 8601 timestamp with timezone (e.g., 2026-03-08T15:30:45+00:00)
customer
object
Customer information
items
array
Order line items
subtotal
number
Total order value (sum of all items)
currency
string
Currency code (always REF for Venezuelan reference)
channel
string
Order channel (always whatsapp)

Example Order Object

{
  "id": "SC-A3X9K2",
  "tenant_id": 123,
  "date": "2026-03-08T15:30:45+00:00",
  "customer": {
    "name": "Juan Pérez",
    "location": "Centro"
  },
  "items": [
    {
      "title": "Camisa Premium",
      "qty": 2,
      "price": 45.50,
      "variant": "Talla M"
    },
    {
      "title": "Pantalón Casual",
      "qty": 1,
      "price": 68.00,
      "variant": null
    }
  ],
  "subtotal": 159.00,
  "currency": "REF",
  "channel": "whatsapp"
}

Storage Architecture

File System Organization

Orders are stored as individual JSON files in the Laravel storage directory:
storage/app/tenants/{tenant_id}/orders/{year}/{month}/{order_id}.json
Example Paths:
storage/app/tenants/123/orders/2026/03/SC-A3X9K2.json
storage/app/tenants/123/orders/2026/03/SC-B7Y5N1.json
storage/app/tenants/456/orders/2026/02/SC-K9M3P8.json

Benefits of File-Based Storage

JSON files provide fast read access without database queries. The controller loads all orders in a single directory scan.
Monthly partitioning prevents directory bloat. Each month starts fresh, keeping file counts manageable.
Year/month folders make it easy to archive old orders or backup specific time periods.
Orders can be easily exported, transferred, or processed by external tools without database dependencies.

Retrieval Process

The OrdersController::index method:
  1. Validates tenant exists and loads plan relationship
  2. Checks if tenant has cat-anual plan
  3. If no access, returns empty view
  4. Scans storage/app/tenants/{tenantId}/orders/ recursively
  5. Filters for .json files only
  6. Decodes each JSON file to array
  7. Validates order has id field
  8. Sorts by date field descending
  9. Returns Blade view with order data
Code Reference: /workspace/source/app/Http/Controllers/OrdersController.php:27-42

Plan Access Control

cat-anual Plan Requirement

Only tenants with the cat-anual plan can:
  • Create orders via the Checkout API
  • View order history via this endpoint
  • Access order management features

Plan Check Logic

$isPlanAnual = $tenant->plan && $tenant->plan->slug === 'cat-anual';
If $isPlanAnual is false:
  • Returns view with empty orders array
  • isPlanAnual set to false in view data
  • UI typically shows upgrade prompt
Tenants without the cat-anual plan will see an empty order list even if order files exist in storage. This is intentional business logic, not a bug.

Date Sorting

Orders are sorted by the date field in descending order (newest first):
usort($orders, fn (array $a, array $b) => strcmp($b['date'] ?? '', $a['date'] ?? ''));

Date Comparison Behavior

  • Uses lexicographic string comparison on ISO 8601 dates
  • Handles missing date fields gracefully (treats as empty string)
  • More recent dates appear first in the array
Example Sorting:
[
  { "id": "SC-NEW123", "date": "2026-03-08T15:30:45+00:00", ... },
  { "id": "SC-MID456", "date": "2026-03-07T10:22:15+00:00", ... },
  { "id": "SC-OLD789", "date": "2026-03-05T08:15:30+00:00", ... }
]

Integration Examples

Dashboard Order Display

<!-- resources/views/dashboard/components/orders-section.blade.php -->

@if($isPlanAnual)
  <div class="orders-list">
    @forelse($orders as $order)
      <div class="order-card">
        <h3>Pedido {{ $order['id'] }}</h3>
        <p class="date">{{ \Carbon\Carbon::parse($order['date'])->format('d/m/Y H:i') }}</p>
        
        <div class="customer">
          <strong>{{ $order['customer']['name'] }}</strong>
          @if($order['customer']['location'])
            <span> {{ $order['customer']['location'] }}</span>
          @endif
        </div>
        
        <ul class="items">
          @foreach($order['items'] as $item)
            <li>
              {{ $item['title'] }}
              @if($item['variant'])
                <span class="variant">({{ $item['variant'] }})</span>
              @endif
              <span class="qty">x{{ $item['qty'] }}</span>
              <span class="price">REF {{ number_format($item['price'], 2) }}</span>
            </li>
          @endforeach
        </ul>
        
        <div class="total">
          <strong>Subtotal: REF {{ number_format($order['subtotal'], 2) }}</strong>
        </div>
      </div>
    @empty
      <p>No hay pedidos registrados.</p>
    @endforelse
  </div>
@else
  <div class="upgrade-prompt">
    <p>Actualiza al plan cat-anual para acceder a la gestión de pedidos.</p>
  </div>
@endif

JavaScript Order Analytics

// Fetch and analyze order data
async function loadOrderAnalytics(tenantId) {
  const response = await fetch(`/tenant/${tenantId}/orders`);
  const html = await response.text();
  
  // Parse HTML or use dedicated JSON endpoint
  const parser = new DOMParser();
  const doc = parser.parseFromString(html, 'text/html');
  
  // Extract order data from rendered HTML
  const orderCards = doc.querySelectorAll('.order-card');
  
  const analytics = {
    totalOrders: orderCards.length,
    totalRevenue: 0,
    topCustomers: {},
    popularProducts: {}
  };
  
  orderCards.forEach(card => {
    // Parse and aggregate data
    const subtotal = parseFloat(
      card.querySelector('.total strong').textContent.match(/[\d,.]+/)[0].replace(',', '.')
    );
    analytics.totalRevenue += subtotal;
    
    // Additional parsing logic...
  });
  
  return analytics;
}
For programmatic access to order data, consider creating a dedicated JSON API endpoint that returns raw order arrays instead of HTML views.

Advanced Order Retrieval

Filtering by Date Range

To retrieve orders from a specific time period, filter the file paths:
use Illuminate\Support\Facades\Storage;

public function getOrdersByMonth(int $tenantId, int $year, int $month): array
{
    $path = "tenants/{$tenantId}/orders/{$year}/" . str_pad($month, 2, '0', STR_PAD_LEFT);
    $files = Storage::disk('local')->files($path);
    
    $orders = [];
    foreach ($files as $file) {
        if (str_ends_with($file, '.json')) {
            $content = Storage::disk('local')->get($file);
            $order = json_decode($content, true);
            if (is_array($order) && isset($order['id'])) {
                $orders[] = $order;
            }
        }
    }
    
    return $orders;
}

Search by Order ID

use Illuminate\Support\Facades\Storage;

public function findOrder(int $tenantId, string $orderId): ?array
{
    // Order ID format: SC-XXXXXX
    $files = Storage::disk('local')->allFiles("tenants/{$tenantId}/orders");
    
    foreach ($files as $file) {
        if (str_contains($file, $orderId . '.json')) {
            $content = Storage::disk('local')->get($file);
            return json_decode($content, true);
        }
    }
    
    return null;
}

Customer Order History

public function getCustomerOrders(int $tenantId, string $customerName): array
{
    $files = Storage::disk('local')->allFiles("tenants/{$tenantId}/orders");
    $customerOrders = [];
    
    foreach ($files as $file) {
        if (!str_ends_with($file, '.json')) continue;
        
        $content = Storage::disk('local')->get($file);
        $order = json_decode($content, true);
        
        if (is_array($order) && 
            isset($order['customer']['name']) &&
            $order['customer']['name'] === $customerName) {
            $customerOrders[] = $order;
        }
    }
    
    // Sort by date descending
    usort($customerOrders, fn($a, $b) => strcmp($b['date'] ?? '', $a['date'] ?? ''));
    
    return $customerOrders;
}

Performance Considerations

File Count Impact

Current Implementation: Scans all order files recursively. Performance may degrade with thousands of orders.
Recommendations:
  • For high-volume tenants, consider adding database indexing
  • Implement pagination for large order sets
  • Cache aggregated statistics (total orders, revenue, etc.)

Directory Scan Optimization

The Storage::allFiles() method recursively scans all subdirectories. For better performance:
// Instead of scanning all files
$allFiles = Storage::disk('local')->allFiles("tenants/{$tenantId}/orders");

// Consider scanning recent months only
$currentYear = date('Y');
$currentMonth = date('m');
$lastMonth = date('m', strtotime('-1 month'));

$recentFiles = array_merge(
    Storage::disk('local')->files("tenants/{$tenantId}/orders/{$currentYear}/{$currentMonth}"),
    Storage::disk('local')->files("tenants/{$tenantId}/orders/{$currentYear}/{$lastMonth}")
);

Caching Strategy

use Illuminate\Support\Facades\Cache;

public function getCachedOrders(int $tenantId): array
{
    $cacheKey = "tenant_{$tenantId}_orders";
    
    return Cache::remember($cacheKey, 300, function () use ($tenantId) {
        // Load orders from storage
        $files = Storage::disk('local')->allFiles("tenants/{$tenantId}/orders");
        // ... existing logic
        return $orders;
    });
}

// Clear cache when new order is created
public function clearOrderCache(int $tenantId): void
{
    Cache::forget("tenant_{$tenantId}_orders");
}

Checkout API

Create new orders and comandas

Tenant Authentication

PIN verification for tenant access

Analytics Tracking

Track order conversion events

Dashboard API

Tenant management endpoints

Troubleshooting

Empty Order List

Symptom: Orders exist in storage but not displayedSolution: Verify $tenant->plan->slug === 'cat-anual'. Upgrade tenant plan if needed.
Symptom: Directory storage/app/tenants/{id}/orders/ is emptySolution: Check if orders were created via Checkout API. Verify file permissions on storage directory.
Symptom: Files exist but not parsed correctlySolution: Validate JSON syntax. Ensure id field exists in each order object.

Incorrect Sorting

Symptom: Orders appear in wrong date order Causes:
  • Missing or null date field in order objects
  • Date format inconsistency (non-ISO 8601)
  • Server timezone configuration issues
Solution: Ensure all orders have valid ISO 8601 date values. Use now()->toIso8601String() when creating orders.

Authentication Errors

Symptom: 401 Unauthorized or redirect to login Solution: This endpoint requires Laravel authentication. Ensure:
  • User is logged in via auth middleware
  • Session cookie is valid
  • User has permission to access the tenant

Future Enhancements

Pagination

Limit orders per page for performance

JSON API Response

Return JSON instead of HTML view

Advanced Filters

Filter by date range, customer, status

Export to CSV/Excel

Download order reports
Consider implementing these features as your order volume grows and tenant needs evolve.

Build docs developers (and LLMs) love