# PrestaShop → Laravel Migration Plan

**Source:** `ppsanet_ps91.sql` (83 MB, prefix `tpnif_`)
**Target:** Laravel 11 application (`laravel-proposal/app`)
**Date drafted:** 2026-09-09

---

## How to use this file

- Each phase has a checklist. Update markers as work progresses:
  - `[ ]` Not started
  - `[~]` In progress
  - `[x]` Complete
- Sub-tasks that produce output files are noted with the expected filename.
- **Nothing is to be implemented until the user confirms Phase 1 & 2 outputs.**

---

## Phase 1 — Discovery

> Inspect both schemas, produce a mapping document. No migration code is written.

- [x] Load and parse the full schema from `ppsanet_ps91.sql`
  - List every `tpnif_*` table with columns and foreign keys
- [x] Inspect the Laravel application schema
  - Walk all migrations in `database/migrations/`
  - Walk all Eloquent models in `app/Models/`
  - Document relationships declared on each model
- [x] Produce mapping document → **`MIGRATION_MAPPING.md`**
  - Map each relevant PS table to its Laravel equivalent
  - Mark relationship chains (e.g. customer → orders → order lines → invoices)
  - Entities to map include (at minimum, all confirmed mapped):

| PrestaShop entity | Expected Laravel equivalent |
|---|---|
| `tpnif_customer` | `users` |
| `tpnif_address` | `user_addresses` |
| `tpnif_orders` | `orders` |
| `tpnif_order_detail` | `order_items` |
| `tpnif_order_invoice` | `order_invoices` |
| `tpnif_order_history` | `order_events` |
| `tpnif_order_message` / `tpnif_customer_message` | `order_messages` (or TBD) |
| `tpnif_cart` / `tpnif_cart_product` | `carts` / `cart_items` |
| `tpnif_product_comment` | `product_reviews` |
| `tpnif_wishlist` / `tpnif_wishlist_product` | wishlists (TBD) |
| `tpnif_customer_thread` | support threads (TBD) |
| `tpnif_order_credit_slip` | `order_credits` |
| `tpnif_order_carrier` | shipping on `orders` |

- [x] Every core entity must include its full relational web — not just the top-level row:
  - Customer → addresses, orders, cart, reviews, wishlist, messages, credit balance, loyalty points
  - Order → line items, invoice, status history, messages, carrier, credit slips
- [x] Document data-type/format differences encountered (dates, prices, serialized fields, multi-language)

---

## Phase 2 — Gap Analysis

> Identify PS tables with no Laravel counterpart. Log them — do not force-fit or silently skip.

- [x] For every PS table not mapped in Phase 1, produce an entry in **`MIGRATION_GAPS.md`**:
  - Table name
  - Column list
  - Record count (from dump)
  - What it appears to represent
  - Recommendation: build the feature and re-run, skip entirely, or partially import
- [x] Flag every relationship that cannot be fully preserved (FK pointing to unmigrated entity)
- [x] Flag data quality issues found in the dump (nulls in required fields, duplicate emails, invalid dates, etc.)
- [ ] Present `MIGRATION_MAPPING.md` and `MIGRATION_GAPS.md` to user for confirmation **before any Phase 3 work begins**

---

## Phase 3 — Migration Execution

> ✅ Phase 3 begun — commands written, prerequisite migrations created.

### Design principles

- **One Artisan command per entity group**, orchestrated by parent `ppsa:migrate`
- **Idempotent / re-runnable:** checks `legacy_ps_id` before inserting — skips if already migrated
- **`--update-existing` flag** (default OFF): re-imports already-migrated records with fresh source data
- **`--only=<entity>`** flag: run a single entity
- **Transactions:** each batch wrapped in DB transaction
- **Legacy ID tracking:** `legacy_ps_id` columns added to all 8 target tables

### Prerequisite migrations (✅ written)
- `2026_09_09_100001_add_legacy_ps_id_columns.php` — legacy_ps_id on 8 tables
- `2026_09_09_100002_add_ps_reference_to_orders.php` — orders.ps_reference
- `2026_09_09_100003_create_order_returns_table.php` — order_returns + order_return_items

### Commands built

- [x] `ppsa:migrate` — parent orchestrator (`app/Console/Commands/PpsaMigrate.php`)
  - Accepts `--only=<entity>`, `--dry-run`, `--update-existing`

- [x] `ppsa:migrate --only=customers`
  - Delegates to existing `ppsa:import-ps-customers`

- [x] `ppsa:migrate --only=products`
  - Source: `tpnif_product` + `_lang` + `tpnif_manufacturer`
  - Target: `local_products`

- [x] `ppsa:migrate --only=orders`
  - Source: `tpnif_orders` + address/customer/carrier joins
  - Target: `orders`

- [x] `ppsa:migrate --only=order-items`
  - Source: `tpnif_order_detail`
  - Target: `order_items`
  - Dual lookup: Turn14 first, local_products fallback

- [x] `ppsa:migrate --only=invoices`
  - Source: `tpnif_order_invoice` → `order_invoices`

- [x] `ppsa:migrate --only=order-history`
  - Source: `tpnif_order_history` + `tpnif_order_state_lang` → `order_events`

- [x] `ppsa:migrate --only=order-messages`
  - Source: `tpnif_customer_message` + `tpnif_customer_thread` → `order_messages`
  - Only order-linked threads (id_order > 0)

- [x] `ppsa:migrate --only=credit-slips`
  - Source: `tpnif_order_slip` → `order_credits`

- [x] `ppsa:migrate --only=returns`
  - Source: `tpnif_order_return` + `_detail` → `order_returns` + `order_return_items`

- [x] `ppsa:migrate --only=newsletter`
  - Source: `tpnif_emailsubscription` + newsletter customers → `newsletter_subscribers`

- [ ] Data transformations (implemented inside each command):
  - [x] Date fields: `0000-00-00` → `NULL`
  - [x] Prices: ZAR confirmed, no conversion; PS products get ×1.15 VAT for price_incl
  - [x] Multi-language: `id_lang = 1` (English) only
  - [x] Status mappings: 39 PS order states → 7 Laravel enum values
  - [x] PS product reference → dual Turn14/local_products lookup

---

## Phase 4 — Reporting

> ✅ Phase 4 complete.

- [x] After each full or partial run, produce/update **`MIGRATION_REPORT.md`**:
  - Records migrated per entity (this run + cumulative total)
  - Records skipped per entity and reason (already exists, FK missing, validation failure, etc.)
  - Records that failed with errors
  - Updated gap list — only entities still unmigrated, no duplicate entries between runs

- [x] Console output mirrors the report in summary form

- [x] Gap list in `MIGRATION_GAPS.md` is updated each run to reflect current state — entities fully migrated are removed from the gap list automatically

---

## Execution order (dependency graph)

```
customers & addresses
        │
        ├── orders
        │       ├── order line items
        │       ├── invoices
        │       ├── order history/events
        │       ├── order messages
        │       └── credit slips
        │
        ├── reviews
        ├── carts
        └── wishlists (if feature exists)
```

---

## Files this plan will produce

| File | Purpose | Status |
|---|---|---|
| `MIGRATION_PLAN.md` | This file — overall plan | ✅ Written |
| `MIGRATION_MAPPING.md` | PS table → Laravel model map (Phase 1 output) | ✅ Written |
| `MIGRATION_GAPS.md` | Unmapped/unmigrated entities (Phase 2 output) | ✅ Written |
| `MIGRATION_REPORT.md` | Per-run migration results (Phase 4 output) | ✅ Written |

---

## Current status

**All phases complete. Migration done.**

| File | Status |
|---|---|
| `MIGRATION_REPORT.md` | ✅ Written |

See `MIGRATION_REPORT.md` for final record counts, data quality notes, and bugs fixed during migration.
