# Plan: Supplier Import Page

## What We're Building

A new tab in the Import plugin — **Import > Suppliers** — that lets you upload a CSV where each row maps a SKU to supplier pricing data. It upserts into `products_product_suppliers`, matching the product by SKU and the supplier by name against `partners_partners`.

---

## CSV Format

Row 1 = headers (case-insensitive). One row per supplier-per-product. A product can have multiple rows if it has multiple suppliers.

| CSV Column | Required | DB Column (products_product_suppliers) | Notes |
|---|---|---|---|
| `sku` | required | via `products_products.sku` → `product_id` | Matches product |
| `supplier_name` | required | via `partners_partners.name` → `partner_id` | Exact match (case-insensitive) |
| `supplier_code` | optional | `product_code` | Supplier's own reference for this product |
| `barcode` | optional | `barcode` | |
| `purchase_price` | optional | `price` | Decimal |
| `tax_percent` | optional | `tax_percentage` | e.g. 20.00 for 20% |
| `currency` | optional | `currency_id` | Match by `currencies.name` e.g. GBP |
| `min_order_qty` | optional | `min_qty` | Decimal |
| `pack_size` | optional | `pack_size` | Decimal |
| `is_default` | optional | `is_default` | 1 or 0 |

**Sample CSV:**
```
sku,supplier_name,supplier_code,barcode,purchase_price,tax_percent,currency,min_order_qty,pack_size,is_default
YZQ100110,Allmakes4x4,YZQ100110,,0.49,,GBP,1,,1
ABC123,Britpart,BP-ABC123,5012345678901,12.50,20.00,GBP,5,10,0
```

---

## Files to Create (7 new files)

### 1. Resource class
```
plugins/webkul/imports/src/Filament/Resources/SupplierImport/SupplierImportResource.php
```
Copy pattern from `ProductImportResource.php`. Change:
- Class name → `SupplierImportResource`
- `$navigationLabel` → `'Suppliers'`
- `getPages()` → points to `ImportSuppliers` page

### 2. Livewire page
```
plugins/webkul/imports/src/Filament/Resources/SupplierImport/Pages/ImportSuppliers.php
```
Copy pattern from `ImportProducts.php`. Key differences:
- Job dispatched → `ProcessSupplierImportJob`
- Import type field not needed (always "update existing products, skip unknown SKUs")
- Form has only: file upload field
- `KNOWN_COLUMNS` constant for the instructions table

### 3. Queue job
```
plugins/webkul/imports/src/Jobs/ProcessSupplierImportJob.php
```
New class — does NOT copy from `ProcessProductImportJob`. Core logic:

```
foreach row:
  1. Find product by SKU → skip if not found
  2. Find partner by supplier_name (case-insensitive) → skip if not found
  3. Find default currency (GBP) for fallback
  4. Look up currency by name from CSV → fall back to GBP if blank/not found
  5. updateOrCreate on products_product_suppliers:
       where: product_id + partner_id
       update: supplier_code, barcode, price, tax_percentage, currency_id,
               min_qty, pack_size, is_default
```

### 4. Blade view
```
plugins/webkul/imports/resources/views/filament/pages/supplier-import.blade.php
```
Same Alpine/Livewire polling pattern as `product-import.blade.php`. Sections:
- Progress section (reuse identical pattern)
- Upload form section
- Import Instructions section (columns table + how it works panel)
- Sample CSV download button (links to a route that serves the template file)

### 5. Sample CSV template file
```
plugins/webkul/imports/resources/templates/supplier-import-template.csv
```
A static file with headers + 2 example rows. Served for download via a route.

### 6. Route for sample download (add to ImportServiceProvider or a routes file)
```php
Route::get('/import-templates/suppliers', function () {
    $path = __DIR__.'/../resources/templates/supplier-import-template.csv';
    return response()->download($path, 'supplier-import-template.csv');
})->middleware(['web', 'auth']);
```

### 7. Navigation lang key
```
plugins/webkul/imports/resources/lang/en/filament/clusters/imports/navigation.php
```
Add: `'suppliers' => 'Suppliers'`

---

## Files to Modify (2 existing files)

### 8. `plugins/webkul/imports/src/ImportPlugin.php`
No changes needed — `discoverResources()` auto-discovers the new `SupplierImportResource`.

### 9. `plugins/webkul/imports/src/Filament/Clusters/Imports.php`
No changes needed — resource auto-joins the cluster.

---

## Core Job Logic (pseudocode)

```php
// ProcessSupplierImportJob::handle()

$colMap = [
    'sku'            => 'sku',
    'supplier_name'  => 'supplier_name',
    'supplier_code'  => 'supplier_code',
    'barcode'        => 'barcode',
    'purchase_price' => 'purchase_price',
    'tax_percent'    => 'tax_percent',
    'currency'       => 'currency',
    'min_order_qty'  => 'min_order_qty',
    'pack_size'      => 'pack_size',
    'is_default'     => 'is_default',
];

// Pre-load lookup caches (avoid N+1 queries)
$products   = Product::pluck('id', 'sku');           // sku => id
$partners   = Partner::pluck('id', DB::raw('LOWER(name)')); // lowercase name => id
$currencies = Currency::pluck('id', 'name');         // GBP => id

$defaultCurrencyId = $currencies['GBP'] ?? Currency::first()->id;

foreach ($rows as $row) {
    $sku = trim($row[$colIndex['sku']]);
    
    $productId = $products[$sku] ?? null;
    if (!$productId) { $skipped++; continue; }  // unknown SKU
    
    $supplierName = strtolower(trim($row[$colIndex['supplier_name']]));
    $partnerId    = $partners[$supplierName] ?? null;
    if (!$partnerId) { $skipped++; continue; }  // unknown supplier name
    
    $currencyName = trim($row[$colIndex['currency']] ?? '');
    $currencyId   = $currencies[$currencyName] ?? $defaultCurrencyId;
    
    $attrs = [
        'currency_id' => $currencyId,
    ];
    if ($get('supplier_code') !== null) $attrs['product_code']    = $get('supplier_code');
    if ($get('barcode')       !== null) $attrs['barcode']         = $get('barcode');
    if ($get('purchase_price')!== null) $attrs['price']           = (float) $get('purchase_price');
    if ($get('tax_percent')   !== null) $attrs['tax_percentage']  = (float) $get('tax_percent');
    if ($get('min_order_qty') !== null) $attrs['min_qty']         = (float) $get('min_order_qty');
    if ($get('pack_size')     !== null) $attrs['pack_size']       = (float) $get('pack_size');
    if ($get('is_default')    !== null) $attrs['is_default']      = parseBool($get('is_default'));
    
    DB::table('products_product_suppliers')->updateOrCreate(
        ['product_id' => $productId, 'partner_id' => $partnerId],
        $attrs
    );
    
    $imported++;
}
```

---

## Key Decisions / Edge Cases

| Situation | Behaviour |
|---|---|
| SKU not found in products | Skip row, count as skipped |
| Supplier name not found in partners_partners | Skip row, count as skipped (do NOT create new partners — that's a separate process) |
| Currency not found | Fall back to GBP (or first active currency) |
| Product already has this supplier | Update existing row (updateOrCreate on product_id + partner_id) |
| Product doesn't have this supplier yet | Insert new row |
| `is_default` = 1 on multiple rows for same product | Last row wins — no automatic clearing of other defaults |
| Blank `currency` cell | Use default currency |

---

## DB Columns Used (products_product_suppliers)

These are the relevant columns from the migration. `company_id`, `creator_id`, `delay`, `discount`, `starts_at`, `ends_at` are NOT imported (left as defaults or null).

```
id              auto
product_id      from SKU lookup
partner_id      from supplier_name lookup
currency_id     from currency name lookup (default GBP)
product_code    ← supplier_code CSV column
barcode         ← barcode CSV column
price           ← purchase_price CSV column
tax_percentage  ← tax_percent CSV column
min_qty         ← min_order_qty CSV column
pack_size       ← pack_size CSV column
is_default      ← is_default CSV column (1/0)
sort            null
discount        0 (default)
delay           0 (default)
creator_id      authenticated user
company_id      null
```

---

## Implementation Order

1. Create `supplier-import-template.csv` (static file, no code)
2. Create `ProcessSupplierImportJob.php` (core logic)
3. Create `SupplierImportResource.php` (minimal wrapper)
4. Create `ImportSuppliers.php` (Livewire page)
5. Create `supplier-import.blade.php` (UI)
6. Add sample download route to service provider
7. Test end-to-end with a small CSV

---

## Notes on `partners_partners` Matching

The partner lookup must be **case-insensitive and trimmed**. Suppliers in the DB often have names like `Allmakes4x4` or `BRITPART`. Use:
```php
DB::table('partners_partners')
    ->whereRaw('LOWER(name) = ?', [strtolower($supplierName)])
    ->value('id');
```

Pre-loading all partners into a cache array (keyed by lowercased name) avoids one query per row for large imports.
