================================================================================
BLUEPRINT: PENYERAGAMAN KEY VARIANT (FORMAT 3 SEGMEN)
================================================================================

1. LATAR BELAKANG
-----------------
- Modul Penjualan: variant:{product_master_id}:{variant_id} (3 segmen)
- Modul Pembelian: variant:{variant_id} (2 segmen)
  => Data tidak konsisten, stok bisa beda.
- Collision risk: variant_id=7 di produk A (id=1759) dan produk B (id=1800)
  sama-sama pakai key "variant:7" di session.
- Key ini dipakai sebagai cart_key di session items (bukan Redis semata).

2. TUJUAN
---------
Semua modul & komponen pakai format: variant:{product_master_id}:{variant_id}

3. RUANG LINGKUP PERUBAHAN
--------------------------
Total ~22 file yang harus diubah, terbagi:

   - Controllers: 5 file + 1 JS view   (construction & parsing)
   - Coms models: 8 file               (parsing variant_id dari key)
   - Mdls models: 3 file               (parsing variant_id dari key)
   - PreProcs:    3 file               (parsing + preg_match)
   - MdlMother:   1 file               (tambah centralized parser)

File-file yang TIDAK tersentuh langsung tapi harus kompatibel:
   - Semua Coms yg hanya terima numeric variant_id (tidak parse key)
   - Semua Mdls LockerStock/LockerStockVariant yg terima parameter numeric

4. KOMPONEN YANG DIPERLUKAN
----------------------------
4.1. MdlMother      - builder key   get_variant_cache_key()
                    - parser key    parse_variant_key()          [BARU]
4.2. Variant_model  - get_key_from_variant_id($variant_id)       [opsional]


5. LANGKAH IMPLEMENTASI
-----------------------

5.1. Tambahkan method di MdlMother.php

    public function get_variant_cache_key($product_master_id, $variant_id) {
        return "variant:{$product_master_id}:{$variant_id}";
    }

    public function parse_variant_key($key) {
        if (!is_string($key) || strpos($key, 'variant:') !== 0) {
            return ['produk_id' => 0, 'variant_id' => 0];
        }
        $parts = explode(':', $key);
        // Format baru: variant:{produk_id}:{variant_id}
        if (count($parts) >= 3 && is_numeric($parts[1]) && is_numeric($parts[2])) {
            return [
                'produk_id'  => (int)$parts[1],
                'variant_id' => (int)$parts[2]
            ];
        }
        // Format lama variant:{variant_id} - TIDAK LAGI DIDUKUNG
        return ['produk_id' => 0, 'variant_id' => 0];
    }


5.2. Buat Variant_model.php (jika belum ada)

    <?php
    class Variant_model extends MdlMother {
        protected $table = 'product_variants';

        public function get_key_from_variant_id($variant_id) {
            $variant = $this->db->get_where($this->table, ['id' => $variant_id])->row();
            if (!$variant) return null;
            return $this->get_variant_cache_key($variant->product_master_id, $variant_id);
        }
    }


5.3. Kontrollers — Ubah Key Construction

    Semua tempat yg membangun string "variant:..." HARUS panggil helper/builder:

    Sebelum:                            Sesudah:
    ----------------------------------  ------------------------------------------
    'variant:' . $variantId             $m->get_variant_cache_key($produkId, $variantId)
    "variant:" . $variantId             $m->get_variant_cache_key($produkId, $variantId)

    Daftar perubahan:

    a) _selectorItem.php:88
         'variant:' . (int)$row->variant_id
         -> $this->Variant_model->get_variant_cache_key($row->id, $row->variant_id)

    b) _selectorItem.php:111
         'variant:' . (int)$row->variant_id
         -> $this->Variant_model->get_variant_cache_key($row->id, $row->variant_id)

    c) _selectorItem.php:597
         "variant:" . $variantId
         -> $this->Variant_model->get_variant_cache_key($produkId, $variantId)

    d) _processSelectProduct.php:37
         'variant:' . $variantId
         -> $this->Variant_model->get_variant_cache_key($produkId, $variantId)

    e) _processSelectProduct.php:192
         'variant:' . $identity['variant_id']
         -> $this->Variant_model->get_variant_cache_key($identity['produk_id'], $identity['variant_id'])

    f) views/variant_picker.php:132  (javascript)
         'variant:' + variantId
         -> 'variant:' + produkId + ':' + variantId
         (INI SATU-SATUNYA yg manual, karena di JS, tidak bisa panggil PHP model)

    g) FollowUp.php:142  resolveFollowupItemKey()
         "variant:" . $variantId
         -> $this->Variant_model->get_variant_cache_key($row->produk_id, $row->variant_id)

    h) FollowUp.php:2315
         "variant:" . $itemVariantId
         -> perlu lookup produk_id dari transaksi_data / session

    i) Printing.php:73  resolvePrintingItemKey()
         "variant:" . $variantId
         -> perlu lookup produk_id dari row

    j) Printing.php:2557
         "variant:" . $variantId
         -> perlu lookup produk_id dari row

    k) Mdls/MdlProdukVarian.php:267
         'variant:' . $row->variant_id
         -> $this->get_variant_cache_key($row->produk_id, $row->variant_id)
         (DALAM MdlMother sendiri, bisa akses langsung method yg sama)


5.4. Controllers — Ubah Key Parsing (ekstrak variant_id dari key)

    Sebelum (str_replace / explode index salah):
         $variantId = (int)str_replace('variant:', '', $someKey);
         $parts = explode(':', $someKey); $variantId = $parts[1];

    Sesudah:
         $parsed = parse_variant_key($someKey);
         $variantId = $parsed['variant_id'];
         // atau langsung dari model:
         $variantId = $this->Variant_model->parse_variant_key($someKey)['variant_id'];

    Daftar perubahan:

    a) _processSelectProduct.php:29,33,155
         str_replace('variant:', '', ...) -> parse_variant_key(...)['variant_id']

    b) _shoppingCart.php:71
         $parts[1] (variant_id) -> $parts[2] (variant_id)
         TAPI LEBIH BAIK: $this->Variant_model->parse_variant_key($cartKey)['variant_id']

    c) FollowUp.php:629-630
         str_replace('variant:', '', ...)  (ini dipakai utk numeric key, bukan variant_id)
         -> parse_variant_key($itemKey)['produk_id']  (karena yg dicari produk_id)


5.5. Models (Coms) — Ubah Parsing variant_id

    Semua tempat yg extract variant_id dari string "variant:..." HARUS panggil
    MdlMother::parse_variant_key() atau helper yg sama.

    Pola perubahan (sama untuk semua file di bawah):

    Sebelum:
        if (strpos($value, 'variant:') === 0) {
            $parts = explode(':', $value);
            $variantId = (int)$parts[1];   // SALAH index 1
        }
        // atau
        $variantId = (int)str_replace('variant:', '', $value);

    Sesudah:
        $parsed = $this->parse_variant_key($value);  // method dari MdlMother
        $variantId = $parsed['variant_id'];

    Daftar file:

    - Coms/ComLockerStock.php:56-57
    - Coms/ComLockerStockVariant.php:70,94
    - Coms/ComLockerStockParentVariant.php:55,75
    - Coms/ComLockerStockMutasi.php:189-190
    - Coms/ComLockerStockMutasiVariant.php:84
    - Coms/ComFifoProdukJadiVarian.php:88
    - Coms/ComPriceProduk.php:214-215
    - Coms/ComPriceProdukLastPurchase.php:213-214
    - Coms/ComPriceProdukPerSupplier.php:206-207
    - Coms/ComRekeningPembantuProduk.php:236 & 352
    - Coms/ComRekeningPembantuProdukRiil.php:212
    - Coms/ComRekeningPembantuProdukVarian.php:242

    CATATAN: ComLockerStock.php:74-79 sudah benar (ambil $tmpEx[2])
             TETAPI tetap perlu diubah panggil parse_variant_key() utk standardisasi.

5.6. Models (Mdls) — Ubah Parsing variant_id

    - Mdls/MdlLockerStockVariant.php:186-187
    - Mdls/MdlLockerStockParentVariant.php:186-187

5.7. PreProcs — Ubah preg_match + Parsing

    - Preprocs/PreLockerStock.php:25,199
        preg_match('/^variant:(\d+)$/', ...) -> parse_variant_key(...)['variant_id']
        $parts[1] -> parse_variant_key(...)['variant_id']

    - Preprocs/PreFifoProdukJadiVarian.php:25
        preg_match -> parse_variant_key(...)['variant_id']

    - Preprocs/PreFifoProdukJadi.php:138
        $parts[1] -> parse_variant_key(...)['variant_id']


5.8. LARANGAN: Jangan pernah menulis "variant:..." manual di luar builder.


6. MIGRASI SESSION (CLI TIDAK CUKUP)
------------------------------------
Session key $_SESSION[$cCode]['items'] TIDAK BISA dimigrasi via Redis CLI.
Migrasi terjadi OTOMATIS saat:

   a) User pilih produk variant (via variantPicker)
      -> cart_key langsung format 3-segment (setelah semua controller di-update)

   b) User next step / approve
      -> FollowUp read dari DB, key dibangun ulang via builder (3-segment)

   c) Session lama dengan key "variant:7" akan:
      - Tidak ditemukan di session setelah update
      - Transaksi tetap jalan karena data dibaca dari transaksi_data (DB)
      - Tidak crash, hanya item variant di cart lama jadi tidak terdeteksi
        (user harus pilih ulang variant untuk session yg sudah ada)

   RISIKO: User dengan session aktif yg mengandung "variant:7" akan kehilangan
   cart items variant-nya saat deploy. Non-variant items tidak terpengaruh.

   MITIGASI: Deploy di luar jam kerja, atau broadcast maintenance.


7. TESTING PLAN
---------------
   - Pilih produk variant di modul penjualan -> cart_key = "variant:1759:7"
   - Simpan transaksi -> session items pakai key baru
   - Approve transaksi -> FollowUp resolve key cocok
   - LockerStock, LockerStockVariant baca variant_id = 7 (dari $parts[2])
   - RekeningPembantuProduk, PreFifoProdukJadi jalan dengan variant_id benar
   - Modul pembelian baca stok variant -> key 3-segment cocok
   - Form shoppingcart ubah qty variant -> item terdeteksi dengan key baru
   - Session lama dengan key 2-segment -> ditolak parse_variant_key (return 0,0)
     -> pastikan tidak fatal error (harusnya hanya qty terbaca 0)


8. ROLLBACK
-----------
   - Kembalikan semua file yg diubah ke versi sebelumnya (git checkout)
   - Tidak perlu rollback session — session lama akan terisi otomatis
   - Tidak perlu rollback Redis — key Redis otomatis terisi saat runtime


9. RISIKO & CATATAN
-------------------
   - MdlMother sudah di-autoload di $autoload['model'] -> tidak perlu require_once.
   - parsing manual di 20+ file -> resiko ada yg terlewat.
     Cara deteksi: grep -r "'variant:'" application/ && grep -r '"variant:"' application/
   - FollowUp.php:2315 dan Printing.php:73,2557 perlu lookup DB untuk dapet produk_id.
     Opsi: JOIN dengan transaksi_data atau product_variants.
   - Tidak ada dual support. Format 2-segment TIDAK AKAN DIKENALI setelah deploy.
     parse_variant_key() mengembalikan ['produk_id'=>0,'variant_id'=>0]
     untuk format variant:{id} (2 segment).

10. CHECKLIST EKSEKUSI
-----------------------

### Fase 1: Prep — Tambah Method di MdlMother + Variant_model  ✅ DONE
- [x] 1. MdlMother.php — tambah method `get_variant_cache_key($produk_id, $variant_id)` — **sudah ada**
- [x] 2. MdlMother.php — tambah method `parse_variant_key($key)`
- [x] 3. Buat `Variant_model.php` — method `get_key_from_variant_id($variant_id)`
- [x] 4. Update blueprint ini jika ada perubahan (hapus Redis migration, ganti fokus ke session)

### Fase 2: Shared Models — Ubah Parsing (18 file) ✅ DONE
- [x] 5. Coms/ComLockerStock.php           — 56-57 (str_replace → helper)
- [x] 6. Coms/ComLockerStockVariant.php    — 70, 94 (explode[1] → helper)
- [x] 7. Coms/ComLockerStockParentVariant.php — 55, 75 (explode[1] → helper)
- [x] 8. Coms/ComLockerStockMutasi.php     — 189-190 (str_replace → helper)
- [x] 9. Coms/ComLockerStockMutasiVariant.php — 84 (explode[1] → helper)
- [x] 10. Coms/ComFifoProdukJadiVarian.php — 88 (explode[1] → helper)
- [x] 11. Coms/ComPriceProduk.php          — 214-215 (str_replace → parse_variant_key)
- [x] 12. Coms/ComPriceProdukLastPurchase.php — 213-214 (str_replace → parse_variant_key)
- [x] 13. Coms/ComPriceProdukPerSupplier.php — 206-207 (str_replace → parse_variant_key)
- [x] 14. Coms/ComRekeningPembantuProduk.php — 236, 352 (preg_match + explode[1] → helper)
- [x] 15. Coms/ComRekeningPembantuProdukRiil.php — 212 (preg_match → helper)
- [x] 16. Coms/ComRekeningPembantuProdukVarian.php — 242 (explode[1] → helper)
- [x] 17. Mdls/MdlLockerStockVariant.php   — 186-187 (str_replace → helper)
- [x] 18. Mdls/MdlLockerStockParentVariant.php — 186-187 (str_replace → helper)
- [x] 19. Mdls/MdlProdukVarian.php          — 267 (key construction → get_variant_cache_key)
- [x] 20. Preprocs/PreLockerStock.php       — 25, 199 (preg_match + explode[1] → helper)
- [x] 21. Preprocs/PreFifoProdukJadiVarian.php — 25 (preg_match → helper)
- [x] 22. Preprocs/PreFifoProdukJadi.php    — 138 (explode[1] → helper)

### Fase 3: Controllers Parsing — Ubah Ekstrak variant_id
- [ ] 23. `_processSelectProduct.php` — per modul:
    - [ ] distribusifg  — 28-29, 33, 154-155
    - [ ] opname        — 28-29, 33, 154-155
    - [ ] pembelian     — 25, 161, 505-506
    - [ ] pembelianimport — 28-29, 32, 157-158
    - [ ] pindahgudang   — 28-29, 31-32, 157-158
    - [ ] requeststok   — 28-29, 33, 154-155
- [ ] 24. `_shoppingCart.php` — per modul:
    - [ ] distribusifg  — 70-71, 308, 313
    - [ ] pindahgudang  — 70-71, 308, 313
- [ ] 25. `_processSelectNotaItem.php` — per modul:
    - [ ] pembelian     — 505-506
    - [ ] pembelianimport — (tidak ada)
- [ ] 26. FollowUp.php — per modul:
    - [ ] distribusifg  — 629-630

### Fase 4: Controllers Construction — Ubah Key Builder (HARUS setelah Fase 2-3 deploy)
- [ ] 27. `_selectorItem.php` — per modul:
    - [ ] distribusifg  — 88, 111, 597
    - [ ] opname        — 88, 111
    - [ ] pembelian     — 88, 111
    - [ ] pembelianimport — 126, 149
    - [ ] pindahgudang  — 88, 111
    - [ ] requeststok   — 88, 111
- [ ] 28. `_processSelectProduct.php` — per modul:
    - [ ] distribusifg  — 37, 192
    - [ ] opname        — 37, 192
    - [ ] pembelian     — 25, 180
    - [ ] pembelianimport — 40, 195
    - [ ] pindahgudang  — 40, 195
    - [ ] requeststok   — 37, 192
- [ ] 29. FollowUp.php — per modul:
    - [ ] distribusifg  — 142, 2315
- [ ] 30. Printing.php — per modul:
    - [ ] distribusifg  — 73, 2557
- [ ] 31. `views/variant_picker.php` (JS) — per modul:
    - [ ] distribusifg  — 132
    - [ ] pembelian     — 132
    - [ ] pindahgudang  — 132
- [ ] 32. `_shoppingCart.php` (JS) — per modul:
    - [ ] konversi      — 18, 50, 1974  (sudah 3-seg, verifikasi saja)

### Fase 5: Deploy & Verifikasi
- [ ] 33. Commit semua perubahan Fase 2-4 dalam 1 commit
- [ ] 34. Deploy ke staging
- [ ] 35. Test: pilih produk variant → cart_key = `variant:{produk_id}:{variant_id}`
- [ ] 36. Test: save transaksi → session items key baru
- [ ] 37. Test: approve transaksi → FollowUp resolve key cocok
- [ ] 38. Test: locker operations (LockerStock, LockerStockVariant) baca variant_id benar
- [ ] 39. Test: ubah qty di shoppingcart → item terdeteksi
- [ ] 40. Broadcast maintenance jika ada session user aktif


11. KESIMPULAN
--------------
Dengan blueprint ini, semua key variant di session, cache, dan parameter internal
menggunakan format seragam: variant:{product_master_id}:{variant_id}.
Semua komponen yang mem-parsing variant key dipusatkan ke MdlMother::parse_variant_key()
sehingga tidak ada lagi ad-hoc str_replace / explode / preg_match di 20+ file.

================================================================================
