# Coding Rules — Blazma LIMS

## 1. Naming Conventions

### 1.1 Database Tables
- **ALL UPPERCASE** for application tables: `END_USER`, `LAB_CATEGORY_PACKAGE`, `HOSPITAL`
- **lowercase** for Laravel system tables: `migrations`, `jobs`, `cache`, `sessions`
- **snake_case lowercase** for diabetic module tables: `diabetic_patient_visits`
- Column names are **UPPERCASE**: `PROFILE_ID`, `END_USER_ID`, `CREATED_BY`
- Primary keys use **ID** (uppercase): `ID int unsigned auto_increment`
- Timestamps remain lowercase: `created_at`, `updated_at`

### 1.2 Models
- **UPPERCASE class names** matching table names: `ENDUSERLABORDER`, `LABCATEGORYPACKAGE`
- Models must declare `protected $table = 'TABLE_NAME'`
- Models in `App\Models\` (top-level) or `App\Models\Base\` (base classes)
- camelCase relationship method names: `labCategoryPackage()`, `endUserLabOrder()`

```php
// CORRECT model example
class ENDUSERLABORDERPACKAGE extends Model
{
    protected $table = 'END_USER_LAB_ORDER_PACKAGE';
    protected $fillable = ['END_USER_LAB_ORDER_ID', 'LAB_CATEGORY_PACKAGE_ID', 'PRICE'];
    
    public function lABCATEGORYPACKAGE()
    {
        return $this->belongsTo(LABCATEGORYPACKAGE::class, 'LAB_CATEGORY_PACKAGE_ID');
    }
}
```

### 1.3 Controllers
- PascalCase: `LabController`, `InvoiceController`, `PayfortController`
- Located in `app/Http/Controllers/`
- Sub-directories for grouped controllers: `V2/`, `Operations/`

### 1.4 Services
- PascalCase with `Service` suffix: `LabService`, `PaymentService`, `HESNPlusService`
- Located in `app/Services/`
- Sub-directories for grouped services: `Alert/`, `ERP/`, `Metadata/`, `Notification/`, `Zatca/`

### 1.5 Jobs
- PascalCase, descriptive names: `ZatcaInvoice`, `NotificationFired`, `HESNPlusSendData`
- Located in `app/Jobs/`
- Should implement `ShouldQueue`

### 1.6 Routes
- Named routes use kebab-case or dot notation: `name('SampleTracking')`, `name('OrderInfo')`
- URL paths use kebab-case: `/order-checkout`, `/reservation-success`

---

## 2. Controller Responsibilities

Controllers must be **thin**. They:

✅ **MUST**:
- Accept HTTP request
- Validate input via `$request->validate()` or FormRequest classes
- Call one or more Service methods
- Return view or JSON response
- Handle HTTP-specific concerns (response codes, redirects)

❌ **MUST NOT**:
- Contain database queries directly
- Contain business logic
- Perform calculations
- Call external APIs directly

```php
// CORRECT — Thin controller
class InvoiceController extends Controller
{
    public function __construct(private TaxService $taxService) {}

    public function generateInvoice(Request $request)
    {
        $request->validate(['order_id' => 'required|integer']);
        $invoice = $this->taxService->generateInvoice($request->order_id);
        return response()->json(['success' => true, 'invoice' => $invoice]);
    }
}

// WRONG — Fat controller with business logic
class InvoiceController extends Controller
{
    public function generateInvoice(Request $request)
    {
        // NEVER DO THIS — business logic in controller
        $order = END_USER_LAB_ORDER::find($request->order_id);
        $total = $order->packages->sum('PRICE');
        $vat = $total * 0.15;
        // ...
    }
}
```

---

## 3. Service Layer Rules

Services contain ALL business logic. They:

✅ **MUST**:
- Be injected via constructor (never instantiated with `new` inside other classes)
- Scope all queries by `PROFILE_ID` (multi-tenancy)
- Dispatch Jobs for heavy/async operations
- Return data arrays, collections, or model instances
- Throw exceptions for error cases (not return false/null silently)

❌ **MUST NOT**:
- Handle HTTP requests/responses
- Access `$_GET`, `$_POST`, or `request()` directly
- Return views or responses

```php
// CORRECT — Service with PROFILE_ID scoping
class LabService
{
    public function getTestsByProfile(int $profileId): Collection
    {
        return LAB_CATEGORY_PACKAGE::where('PROFILE_ID', $profileId)
            ->where('IS_ACTIVE', 1)
            ->get();
    }
    
    public function processResult(int $packageId, array $results): void
    {
        // business logic
        SmartReportReady::dispatch($packageId); // async job
    }
}
```

---

## 4. Multi-Tenancy Rules

**CRITICAL**: Every query on business tables MUST be scoped by `PROFILE_ID`.

```php
// ✅ CORRECT
LAB_CATEGORY_PACKAGE::where('PROFILE_ID', $profileId)->get();

// ✅ CORRECT with join
END_USER_LAB_ORDER::where('PROFILE_ID', $profileId)
    ->where('HOSPITAL_ID', $hospitalId)
    ->get();

// ❌ WRONG — returns all tenants' data
LAB_CATEGORY_PACKAGE::all();
LAB_CATEGORY_PACKAGE::where('IS_ACTIVE', 1)->get();
```

The active `PROFILE_ID` is typically retrieved from:
- Environment: `env('PROFILE_ID')` (white-label mode)
- Session/Auth: authenticated user's profile
- Request parameter (for multi-profile users)

---

## 5. Queue / Job Rules

Heavy operations MUST be dispatched as jobs:

```php
// ✅ CORRECT — Heavy operations are async
ZatcaInvoice::dispatch($invoiceId);
SendSMSJob::dispatch($mobile, $message);
HESNPlusSendData::dispatch($orderId);
GenerateAiRecommendationJob::dispatch($userId);

// ❌ WRONG — Synchronous heavy operation in request lifecycle
$this->zatcaService->generateInvoice($invoiceId); // blocks HTTP response
$this->smsService->send($mobile, $message); // blocks HTTP response
```

Job classes must:
- Implement `ShouldQueue`
- Use `implements ShouldQueue` and `use Queueable, InteractsWithQueue, SerializesModels`
- Handle failures gracefully (retry/log)

---

## 6. Validation Patterns

Use inline validation for simple cases, FormRequest for complex:

```php
// Inline validation
$request->validate([
    'PROFILE_ID'  => 'required|integer|exists:PROFILE,ID',
    'HOSPITAL_ID' => 'required|integer|exists:HOSPITAL,ID',
    'NAME_EN'     => 'required|string|max:255',
    'NAME_AR'     => 'nullable|string|max:255',
]);

// FormRequest class (for reuse across endpoints)
class CreateOrderRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'tests'           => 'required|array|min:1',
            'tests.*.id'      => 'required|integer',
            'hospital_id'     => 'required|integer',
            'appointment_date' => 'required|date|after:today',
        ];
    }
}
```

---

## 7. Bilingual Data Pattern

All user-facing content must have both Arabic and English:

```php
// ✅ CORRECT — Always save both languages
$category = LAB_CATEGORY::create([
    'NAME_EN' => $request->name_en,
    'NAME_AR' => $request->name_ar,    // required
    'PROFILE_ID' => $profileId,
]);

// For responses, return both
return [
    'name' => [
        'en' => $category->NAME_EN,
        'ar' => $category->NAME_AR,
    ]
];
```

---

## 8. Feature Flag Pattern

Always check feature flags before executing feature-specific code:

```php
// ✅ CORRECT — Check feature flag first
$profile = PROFILE::find($profileId);

if ($profile->ENABLE_QC) {
    // QC module logic
}

if ($profile->ENABLE_AI_RECOMMENDATION) {
    GenerateAiRecommendationJob::dispatch($orderId);
}

if ($profile->ENABLE_WAREHOUSE) {
    // Deduct reagent from inventory
}
```

---

## 9. Response Format

### API (JSON) responses:
```php
// Success
return response()->json([
    'success' => true,
    'data'    => $result,
    'message' => 'Operation successful',
]);

// Error
return response()->json([
    'success' => false,
    'message' => 'Error description',
    'errors'  => $validationErrors,
], 422);
```

### Web (Blade) responses:
```php
return view('page.name', compact('data', 'profile'));
```

---

## 10. Eloquent Usage Rules

```php
// ✅ CORRECT — Eager loading to prevent N+1
$orders = END_USER_LAB_ORDER::with([
    'endUser',
    'packages.labCategoryPackage',
    'hospital',
])->where('PROFILE_ID', $profileId)->get();

// ✅ CORRECT — Use specific columns
$users = END_USER::select('ID', 'FULL_NAME', 'MOBILE_NUMBER')
    ->where('PROFILE_ID', $profileId)
    ->get();

// ❌ WRONG — N+1 query problem
$orders = END_USER_LAB_ORDER::where('PROFILE_ID', $profileId)->get();
foreach ($orders as $order) {
    $user = $order->endUser; // N+1!
}

// ❌ WRONG — Select all columns unnecessarily
$users = END_USER::where('PROFILE_ID', $profileId)->get(); // loads 100+ columns
```

---

## 11. File & Asset Storage

```php
// ✅ CORRECT — Use FileService for uploads
$path = $this->fileService->uploadToS3($file, 'reports/');

// ✅ CORRECT — Store path (not full URL) in DB
$record->update(['IMAGE_PATH' => $path]);

// ✅ CORRECT — Generate URL when needed
$url = Storage::disk('s3')->url($record->IMAGE_PATH);
```

---

## 12. Error Logging

```php
// ✅ CORRECT — Use LogService or Laravel logging
\Log::error('HESN Plus API failed', [
    'profile_id' => $profileId,
    'order_id'   => $orderId,
    'error'      => $e->getMessage(),
]);

// Also use system log tables
$this->logService->log('HESN_SEND', $orderId, $response);
```

---

## 13. Migration Rules

```php
// ✅ CORRECT migration naming
// YYYY_MM_DD_HHMMSS_description.php
// 2025_01_15_103000_add_enable_loyalty_to_profile_table.php

Schema::table('PROFILE', function (Blueprint $table) {
    $table->tinyInteger('ENABLE_LOYALTY')->default(0)->after('SUSPENSION_PERIOD');
});

// ✅ CORRECT — Always add down() method
public function down(): void
{
    Schema::table('PROFILE', function (Blueprint $table) {
        $table->dropColumn('ENABLE_LOYALTY');
    });
}
```

---

## 14. Documentation Rule

When modifying architecture, modules, or significant logic:
1. Update the relevant file in `/docs/ai/`
2. Update `AI_SYSTEM_MAP.json` if new modules, tables, or services are added
3. Run `php artisan ai:update-docs` after each sprint
