cd /mnt/c/xampp/htdocs/medusa_totti/apps/backend && yarn add @medusajs/file-local@^2.15.2 @medusajs/file@^2.15.2 ================================================================================ NOTES — Panduan Input Product di MedusaJS v2 Project: totti-commerce (playtotti.com product migration) ================================================================================ ⚠️ UPDATE 13 Juni 2026 — Volumetric Weight untuk RajaOngkir Modul RajaOngkir sekarang sudah pakai volumetric weight! Rumus: (length × width × height) / 6000 per item, lalu dibandingkan dengan actual weight. Dipakai yang terbesar. WAJIB: Isi length, width, height di setiap variant selain weight agar ongkir yang dihitung akurat. Jika tidak diisi, volumetric = 0 (jadi fallback ke actual weight saja). ================================================================================ Berikut adalah penjelasan setiap field/input yang digunakan saat membuat produk di MedusaJS v2. Field-field ini digunakan baik via Admin Dashboard maupun via migration seed script (createProductsWorkflow). ================================================================================ 1. STRUKTUR DASAR PRODUK ================================================================================ Setiap produk adalah object dengan struktur sebagai berikut: { title: "Nama Produk", // Wajib — Nama produk subtitle: "Subtitle", // Opsional — Teks pendek di bawah judul description: "Deskripsi panjang...", // Opsional — Deskripsi lengkap produk handle: "nama-produk", // Opsional — URL slug, auto-generate dari title status: "published", // "draft" | "published" | "proposed" | "rejected" thumbnail: "https://...", // Opsional — URL thumbnail weight: 400, // Opsional — Berat dalam gram length: 30, // Opsional — Panjang dalam cm width: 20, // Opsional — Lebar dalam cm height: 10, // Opsional — Tinggi dalam cm origin_country: "ID", // Opsional — Negara asal (ISO 2 digit) hs_code: "9503.00", // Opsional — HS Code untuk bea cukai mid_code: "", // Opsional — MID Code material: "Cotton", // Opsional — Bahan/material discountable: true, // Boolean — Apakah bisa didiskon is_giftcard: false, // Boolean — Apakah ini gift card collection_id: "col_...", // Opsional — ID Collection type_id: "ptyp_...", // Opsional — ID Product Type shipping_profile_id: "sp_...", // Wajib — ID Shipping Profile categories: [], // Array kategori (lihat section 5) images: [], // Array gambar (lihat section 4) options: [], // Array opsi (lihat section 2) variants: [], // Array varian (lihat section 3) sales_channels: [], // Array sales channel (lihat section 6) metadata: {}, // Object key-value custom tags: [], // Array tag (lihat section 5) } ================================================================================ 2. OPTIONS ================================================================================ "Options" adalah kerangka yang mendefinisikan bagaimana varian-varian produk dibedakan. Contoh: Size, Color, Material, Ukuran, dll. Struktur: { title: "Size", // Nama opsi (Size, Color, Ukuran, dll) values: ["S", "M", "L"] // Nilai-nilai yang mungkin } Aturan: - Setiap produk minimal punya 1 option - Option bersifat global untuk satu produk (semua varian share option yang sama) - Contoh baju: option = Size [S, M, L, XL], Color [Black, White] → Maka varian akan menjadi kombinasi: S-Black, S-White, M-Black, M-White, dll. - Produk simple (1 harga, 1 tipe) cukup 1 option dengan 1 value (contoh: "Ukuran" → "Standar") Contoh 1 (Multiple variants — Kixplore Standar): options: [ { title: "Variant", values: ["3 in 1", "6 in 1"] } ] → 2 varian: "3 in 1" dan "6 in 1" Contoh 2 (Single variant — Kapur Chalkies): options: [ { title: "Type", values: ["Standar"] } ] → 1 varian: "Standar" Contoh 3 (Balance Beam — Panjang & Warna): options: [ { title: "Panjang", values: ["30cm", "60cm", "90cm"] }, { title: "Warna", values: ["Natural", "Pastel"] } ] → 6 varian: 30cm-Natural, 30cm-Pastel, 60cm-Natural, dst. ================================================================================ 3. VARIANTS ================================================================================ "Variants" adalah realisasi konkret dari kombinasi options. Setiap varian punya harga, SKU, stok, dan atributnya sendiri. Struktur: { title: "S / Black", // Nama varian sku: "SHIRT-S-BLACK", // SKU unik barcode: "", // Barcode (opsional) ean: "", // European Article Number (opsional) upc: "", // Universal Product Code (opsional) variant_rank: 0, // Urutan tampilan (opsional) options: { Size: "S", // Key = Title option, Value = Value yang dipilih Color: "Black" }, prices: [ { amount: 100000, currency_code: "idr", region_id: "reg_..." } ], manage_inventory: true, // Apakah inventory di-track allow_backorder: false, // Boleh backorder? inventory_quantity: 100, // Quantity (hanya via workflow) weight: 400, // Override weight (opsional) length: 30, // Override dimensi (opsional) width: 20, height: 10, material: "Cotton", // Override material (opsional) metadata: {} // Custom field per varian } Aturan: - Jumlah varian = perkalian dari semua nilai options Contoh: option Size [S, M, L] x Color [Black, White] = 6 varian - Setiap varian harus punya option mapping yang valid - options key harus SAMA PERSIS dengan option title - Minimal 1 varian per produk Cara nentuin varian dari produk Toko: - "Rp35,000 – Rp490,000" → Multiple variants (ada range harga) - "Rp13,500" → Single variant (1 harga tetap) - Variasi bisa dari: ukuran (panjang/tinggi), jumlah isi, warna, material ================================================================================ 4. IMAGES ================================================================================ Array gambar produk. Urutan pertama biasanya jadi thumbnail. Format: images: [ { url: "https://..." }, { url: "https://..." }, ] Aturan: - URL bisa dari hosted image (S3, etc.) atau upload via admin - Untuk seed script, pakai URL langsung ================================================================================ 5. CATEGORIES, TAGS & TYPES ================================================================================ Ada 3 cara mengelompokkan produk di Medusa: a) Product Categories (Kategori) Struktur: { id: "pcat_..." } Bisa hierarchical (parent-child). Di seed: category_ids: ["pcat_..."] b) Product Tags (Tag) Array of { id: "ptag_..." } atau { value: "tag-name" } Untuk filtering/label tambahan. c) Product Types (Tipe) Satu produk punya 1 tipe: { id: "ptyp_..." } Contoh: "Physical", "Digital", "Rental" Perbedaan: - Categories → untuk navigasi toko (orang belanja lihat kategori) - Tags → untuk internal filtering / search - Types → untuk klasifikasi jenis produk Untuk totti, mapping kategori dari playtotti.com: → Sensory Toys → Fine Motor Toys → Movement Toys → Construction And Open-Ended Toys → Kids Worksheet (Digital Product) → Rental ================================================================================ 6. SALES CHANNELS ================================================================================ "Sales Channels" menentukan di channel mana produk bisa dijual. Struktur: sales_channels: [ { id: "sc_..." } ] Aturan: - Produk harus terhubung ke minimal 1 sales channel - Default: "Default Sales Channel" (terbuat otomatis saat seed) - Storefront hanya menampilkan produk dari sales channel terkait - Publishable API Key terhubung ke sales channel tertentu ================================================================================ 7. COLLECTIONS ================================================================================ "Collections" adalah grup produk yang ditampilkan bersama (contoh: "Summer Collection"). Struktur: collection_id: "col_..." Aturan: - Opsional - Satu produk bisa masuk ke 1 collection - Bisa dibuat via createCollectionsWorkflow ================================================================================ 8. PRICES ================================================================================ Setiap varian punya array prices. Di Medusa v2, harga bisa diset per currency atau per region. [ { amount: 13500, currency_code: "idr" }, { amount: 100000, currency_code: "idr", region_id: "reg_..." } ] Aturan: - Minimal 1 price per variant - Bisa pakai currency_code saja (berlaku untuk semua region dengan currency itu) - Bisa spesifik ke region_id - Untuk Indonesia: currency_code = "idr" - Amount dalam unit terkecil (Rupiah: tanpa koma, 13.500 = 13500) ================================================================================ 9. METADATA ================================================================================ "Metadata" adalah object key-value untuk data custom yang tidak termasuk field standar. Berguna untuk integrasi/modul kustom. metadata: { rajaongkir_subdistrict_id: "17596", // Contoh untuk shipping warna_tersedia: ["Merah", "Biru"], bahan: "Kayu Pinus", usia_minimal: 2, } Aturan: - Key/value berupa string - Bisa digunakan di kustom module (contoh: rajaongkir-fulfillment) - Tidak ditampilkan di storefront secara default (perlu kustomisasi) ================================================================================ 10. SHIPPING PROFILE ================================================================================ "Shipping Profile" menentukan aturan pengiriman untuk produk. Biasanya ada 3 default: Default, Digital, Gift Card. - Produk fisik → Default shipping profile - Produk digital → Digital shipping profile (tidak perlu dikirim) - Untuk totti: main produk pakai Default, worksheet digital pakai Digital ================================================================================ 11. INVENTORY / STOK ================================================================================ Inventory di Medusa v2 menggunakan module terpisah (Inventory Module). - manage_inventory: true → stok di-track - allow_backorder: false → tidak boleh beli jika stok habis - Inventory levels: jumlah stok per location { location_id: "sloc_...", stocked_quantity: 100, inventory_item_id: "iitem_..." } Di seed, setelah produk dibuat, inventory items otomatis terbuat. Kita tinggal set inventory levels via createInventoryLevelsWorkflow. ================================================================================ 12. FLOW SEED PRODUK (via migration script) ================================================================================ 1. Buat Categories (createProductCategoriesWorkflow) 2. Buat Collections (createCollectionsWorkflow) 3. Buat Products + Options + Variants (createProductsWorkflow) 4. Set Inventory Levels (createInventoryLevelsWorkflow) createProductsWorkflow otomatis: - Membuat products - Membuat variants - Membuat prices - Membuat options - Menghubungkan ke categories - Menghubungkan ke sales channels - Membuat inventory items untuk setiap variant ================================================================================ 13. TIPS UNTUK PRODUK TOTTI PLAY ================================================================================ ⚠️ WAJIB DIISI — Weight + Dimensi untuk Ongkir Setiap variant WAJIB diisi: - weight (gram) → untuk actual weight - length, width, height (cm) → untuk volumetric weight Jika tidak diisi, ongkir bisa tidak akurat karena volumetric = 0. Produk dengan range harga: "Rp35,000 – Rp490,000" → Butuh multiple variants Tentukan apa yang bikin harga beda (ukuran? jumlah isi? varian?). Contoh Manouvre Balance Beam: Mungkin varian berdasarkan panjang (30cm, 60cm, 90cm, 120cm, set). options: [{ title: "Tipe", values: ["30cm", "60cm", "90cm", "Set 4 pcs"] }] prices: [135000, 275000, ...] Produk dengan harga tunggal: "Rp13,500" → 1 variant saja. options: [{ title: "Tipe", values: ["Standar"] }] Produk Digital (Kids Worksheet): → shipping_profile_id khusus digital → weight = 0 (atau tidak ada) → is_giftcard = false Produk Rental (Sewa): → Bisa kategori khusus "Rental" → Atau pakai metadata: { type: "rental" } → Harga harga sewa per hari/per paket ================================================================================