prepare( "SELECT bin FROM md_bin WHERE company_id = :c AND warehouse = :w AND product_sku IS NULL ORDER BY id ASC LIMIT 1" ); $sth->execute([':c' => $company_id, ':w' => $warehouse_id]); $bin = $sth->fetchColumn(); if ($bin === false) { throw new Exception("demo_seed.php: no free bin available in warehouse #{$warehouse_id}."); } return $bin; } echo "\n\033[1m=== brnwms Demo Data Seeder ===\033[0m\n\n"; // ───────────────────────────────────────────────────────────────────────────── // 1. Owner user + company (pdo1 = wms auth/identity db) // ───────────────────────────────────────────────────────────────────────────── echo "--- Owner account & company ---\n"; $demo_username = 'admin'; $demo_email = 'thanakorn.inbox@gmail.com'; $demo_password = 'Demo@12345'; $demo_channel = 'demo'; $sth = $pdo1->prepare("SELECT user_id FROM user WHERE username = :u OR email = :e LIMIT 1"); $sth->execute([':u' => $demo_username, ':e' => $demo_email]); $owner_user_id = (int)($sth->fetchColumn() ?: 0); if ($owner_user_id) { skip("User '{$demo_username}' already exists (user_id={$owner_user_id})"); } else { $hashed = password_hash($demo_password, PASSWORD_BCRYPT); $sth = $pdo1->prepare(" INSERT INTO user (username, name, surname, email, password, status, profile_picture, license, app_access) VALUES (:username, :name, :surname, :email, :password, 'active', '', 'owner', 'all') "); $sth->execute([ ':username' => $demo_username, ':name' => 'Thanakorn', ':surname' => 'Admin', ':email' => $demo_email, ':password' => $hashed, ]); $owner_user_id = (int)$pdo1->lastInsertId(); ok("Created user '{$demo_username}' (user_id={$owner_user_id}), password: {$demo_password}"); } $sth = $pdo1->prepare("SELECT company_id FROM company_list WHERE channel_name = :c LIMIT 1"); $sth->execute([':c' => $demo_channel]); $company_id = (int)($sth->fetchColumn() ?: 0); if ($company_id) { skip("Company channel '{$demo_channel}' already exists (company_id={$company_id})"); } else { $sth = $pdo1->prepare(" INSERT INTO company_list (channel_name, company_name, company_name2, branch, branch_no, email, phone, fx) VALUES (:channel_name, :company_name, :company_name2, :branch, :branch_no, :email, :phone, 'thb') "); $sth->execute([ ':channel_name' => $demo_channel, ':company_name' => 'Demo Trading Co., Ltd.', ':company_name2' => 'Demo Trading', ':branch' => 'สำนักงานใหญ่', ':branch_no' => '00000', ':email' => $demo_email, ':phone' => '021234567', ]); $company_id = (int)$pdo1->lastInsertId(); ok("Created company 'Demo Trading Co., Ltd.' (company_id={$company_id})"); } $sth = $pdo1->prepare("SELECT 1 FROM company_map_user WHERE company_id = :c AND user_id = :u LIMIT 1"); $sth->execute([':c' => $company_id, ':u' => $owner_user_id]); if ($sth->fetchColumn()) { skip("company_map_user link already exists"); } else { $pdo1->prepare(" INSERT INTO company_map_user (company_id, user_id, role, app_access, created_at) VALUES (:company_id, :user_id, 'owner', 'all', NOW()) ")->execute([':company_id' => $company_id, ':user_id' => $owner_user_id]); ok("Linked user to company as owner"); } $pdo1->prepare("UPDATE user SET default_company = :c WHERE user_id = :u AND (default_company IS NULL OR default_company = 0)") ->execute([':c' => $company_id, ':u' => $owner_user_id]); // No company_smtp row is inserted on purpose — login_otp.php skips the OTP // step entirely when a company has no SMTP configured. $logging = ['user_id' => $owner_user_id, 'dt' => date('Y-m-d H:i:s'), 'login' => null, 'action' => 'seed']; // ───────────────────────────────────────────────────────────────────────────── // 2. Warehouses + storage/bins (pdo2 = wms2 business db) // ───────────────────────────────────────────────────────────────────────────── echo "\n--- Warehouses ---\n"; $whMgmt = new WarehouseManager($pdo2, $company_id); $warehouse_defs = [ ['name' => 'Main Warehouse', 'location' => '123 Sukhumvit Rd, Bangkok', 'bin_to' => 30], ['name' => 'Bangna Distribution Center', 'location' => '456 Bangna-Trad Rd, Samut Prakan', 'bin_to' => 15], ]; $warehouse_ids = []; // name => id foreach ($warehouse_defs as $wh) { $sth = $pdo2->prepare("SELECT id FROM md_warehouse WHERE company_id = :c AND warehouse_name = :n LIMIT 1"); $sth->execute([':c' => $company_id, ':n' => $wh['name']]); $id = (int)($sth->fetchColumn() ?: 0); if ($id) { skip("Warehouse '{$wh['name']}' already exists (id={$id})"); } else { dbTransaction($pdo2, function ($pdo2) use ($whMgmt, $wh, $company_id, $owner_user_id, $logging, &$id) { $id = $whMgmt->saveWarehouse([ 'id' => 0, 'warehouse_name' => $wh['name'], 'location' => $wh['location'], 'manager' => $owner_user_id, 'description' => 'Demo warehouse', 'status' => 1, ], $logging); }); $whMgmt->createStockTableForWarehouse($id); ok("Created warehouse '{$wh['name']}' (id={$id}) + td_stock_{$id} table"); } $warehouse_ids[$wh['name']] = $id; // Simple-mode storage: one bin range "A-1".."A-N" (zone=aisle=bin=same value) $sth = $pdo2->prepare("SELECT id FROM md_storage WHERE company_id = :c AND warehouse = :w LIMIT 1"); $sth->execute([':c' => $company_id, ':w' => $id]); if ($sth->fetchColumn()) { skip("Storage range for '{$wh['name']}' already exists"); } else { dbTransaction($pdo2, function ($pdo2) use ($whMgmt, $id, $wh, $logging) { $whMgmt->saveStorage([ 'id' => 0, 'warehouse' => (string)$id, 'bin_from' => 'A-1', 'bin_to' => 'A-' . $wh['bin_to'], 'description' => 'Demo storage range', 'status' => 1, ], $logging, false); }); ok("Created storage range A-1..A-{$wh['bin_to']} for '{$wh['name']}' ({$wh['bin_to']} bins)"); } } $main_wh_id = $warehouse_ids['Main Warehouse']; $bangna_wh_id = $warehouse_ids['Bangna Distribution Center']; // ───────────────────────────────────────────────────────────────────────────── // 3. Product categories + products // ───────────────────────────────────────────────────────────────────────────── echo "\n--- Products ---\n"; $prodMgmt = new ProductManager($pdo2, $company_id); $category_defs = [ 'electronics' => 'Electronics', 'office-supplies' => 'Office Supplies', 'beverages' => 'Beverages', 'packaging-materials' => 'Packaging Materials', ]; $category_ids = []; // slug => id foreach ($category_defs as $slug => $name) { $sth = $pdo2->prepare("SELECT id FROM md_product_category WHERE company_id = :c AND slug = :s LIMIT 1"); $sth->execute([':c' => $company_id, ':s' => $slug]); $id = (int)($sth->fetchColumn() ?: 0); if ($id) { skip("Category '{$name}' already exists (id={$id})"); } else { dbTransaction($pdo2, function ($pdo2) use ($prodMgmt, $name, $slug, $logging) { $prodMgmt->saveCategory([ 'id' => 0, 'category' => $name, 'slug' => $slug, 'description' => '', 'status' => 1, ], $logging); }); $sth = $pdo2->prepare("SELECT id FROM md_product_category WHERE company_id = :c AND slug = :s LIMIT 1"); $sth->execute([':c' => $company_id, ':s' => $slug]); $id = (int)$sth->fetchColumn(); ok("Created category '{$name}' (id={$id})"); } $category_ids[$slug] = $id; } $product_defs = [ ['sku' => 'EL-001', 'name' => 'Wireless Mouse', 'cat' => 'electronics', 'uom' => 'pcs', 'price' => 590, 'cost' => 350, 'min' => 10, 'reorder' => 20, 'qty' => 120], ['sku' => 'EL-002', 'name' => 'USB-C Cable 1m', 'cat' => 'electronics', 'uom' => 'pcs', 'price' => 190, 'cost' => 90, 'min' => 20, 'reorder' => 40, 'qty' => 250], ['sku' => 'EL-003', 'name' => 'Bluetooth Speaker', 'cat' => 'electronics', 'uom' => 'pcs', 'price' => 1290, 'cost' => 780, 'min' => 5, 'reorder' => 10, 'qty' => 45], ['sku' => 'EL-004', 'name' => 'Power Bank 10000mAh', 'cat' => 'electronics', 'uom' => 'pcs', 'price' => 890, 'cost' => 520, 'min' => 8, 'reorder' => 15, 'qty' => 60], ['sku' => 'OF-001', 'name' => 'A4 Paper Ream', 'cat' => 'office-supplies', 'uom' => 'ream', 'price' => 120, 'cost' => 75, 'min' => 30, 'reorder' => 60, 'qty' => 300], ['sku' => 'OF-002', 'name' => 'Ballpoint Pen (Box of 12)', 'cat' => 'office-supplies', 'uom' => 'box', 'price' => 85, 'cost' => 45, 'min' => 15, 'reorder' => 30, 'qty' => 150], ['sku' => 'OF-003', 'name' => 'Stapler', 'cat' => 'office-supplies', 'uom' => 'pcs', 'price' => 65, 'cost' => 35, 'min' => 10, 'reorder' => 20, 'qty' => 80], ['sku' => 'OF-004', 'name' => 'Sticky Notes Pack', 'cat' => 'office-supplies', 'uom' => 'pack', 'price' => 45, 'cost' => 22, 'min' => 20, 'reorder' => 40, 'qty' => 200], ['sku' => 'BV-001', 'name' => 'Bottled Water 600ml (Pack of 12)', 'cat' => 'beverages', 'uom' => 'pack', 'price' => 72, 'cost' => 48, 'min' => 25, 'reorder' => 50, 'qty' => 180], ['sku' => 'BV-002', 'name' => 'Instant Coffee 3-in-1 (Box of 30)', 'cat' => 'beverages', 'uom' => 'box', 'price' => 145, 'cost' => 98, 'min' => 15, 'reorder' => 30, 'qty' => 90], ['sku' => 'BV-003', 'name' => 'Green Tea Bags (Box of 25)', 'cat' => 'beverages', 'uom' => 'box', 'price' => 89, 'cost' => 55, 'min' => 12, 'reorder' => 24, 'qty' => 70], ['sku' => 'PK-001', 'name' => 'Corrugated Box Medium', 'cat' => 'packaging-materials', 'uom' => 'pcs', 'price' => 18, 'cost' => 9, 'min' => 100, 'reorder' => 200, 'qty' => 500], ['sku' => 'PK-002', 'name' => 'Bubble Wrap Roll', 'cat' => 'packaging-materials', 'uom' => 'roll', 'price' => 250, 'cost' => 160, 'min' => 10, 'reorder' => 20, 'qty' => 40], ['sku' => 'PK-003', 'name' => 'Packing Tape', 'cat' => 'packaging-materials', 'uom' => 'pcs', 'price' => 35, 'cost' => 18, 'min' => 40, 'reorder' => 80, 'qty' => 220], ]; foreach ($product_defs as $p) { $sth = $pdo2->prepare("SELECT id FROM md_product WHERE company_id = :c AND sku = :s LIMIT 1"); $sth->execute([':c' => $company_id, ':s' => $p['sku']]); if ($sth->fetchColumn()) { skip("Product '{$p['sku']}' already exists"); continue; } dbTransaction($pdo2, function ($pdo2) use ($prodMgmt, $p, $category_ids, $logging) { $prodMgmt->saveProduct([ 'id' => 0, 'product_name' => $p['name'], 'sku' => $p['sku'], 'barcode' => '', 'uom' => $p['uom'], 'price' => $p['price'], 'cost_price' => $p['cost'], 'min_stock' => $p['min'], 'reorder_point' => $p['reorder'], 'category' => $category_ids[$p['cat']], 'description' => '', 'status' => 1, ], $logging, ''); }); ok("Created product {$p['sku']} — {$p['name']}"); } // ───────────────────────────────────────────────────────────────────────────── // 4. Contact types + contacts // ───────────────────────────────────────────────────────────────────────────── echo "\n--- Contacts ---\n"; $contactMgmt = new ContactManager($pdo2, $company_id); $contact_type_defs = ['Customer', 'Supplier']; $contact_type_ids = []; foreach ($contact_type_defs as $name) { $sth = $pdo2->prepare("SELECT id FROM md_contact_type WHERE company_id = :c AND contact_type = :n LIMIT 1"); $sth->execute([':c' => $company_id, ':n' => $name]); $id = (int)($sth->fetchColumn() ?: 0); if ($id) { skip("Contact type '{$name}' already exists (id={$id})"); } else { dbTransaction($pdo2, function ($pdo2) use ($contactMgmt, $name, $logging) { $contactMgmt->saveContactType([ 'id' => 0, 'contact_type' => $name, 'description' => '', 'status' => 1, ], $logging); }); $sth = $pdo2->prepare("SELECT id FROM md_contact_type WHERE company_id = :c AND contact_type = :n LIMIT 1"); $sth->execute([':c' => $company_id, ':n' => $name]); $id = (int)$sth->fetchColumn(); ok("Created contact type '{$name}' (id={$id})"); } $contact_type_ids[$name] = $id; } $contact_defs = [ ['name' => 'Siam Retail Co., Ltd.', 'type' => 'Customer', 'tax' => '0105561012341'], ['name' => 'Bangkok Mart', 'type' => 'Customer', 'tax' => '0105561012342'], ['name' => 'Northern Distribution Partners', 'type' => 'Customer', 'tax' => '0105561012343'], ['name' => 'Chao Phraya Trading', 'type' => 'Customer', 'tax' => '0105561012344'], ['name' => 'Global Parts Supply Co., Ltd.', 'type' => 'Supplier', 'tax' => '0105561098761'], ['name' => 'Thai Packaging Industries', 'type' => 'Supplier', 'tax' => '0105561098762'], ['name' => 'Asia Electronics Wholesale', 'type' => 'Supplier', 'tax' => '0105561098763'], ]; $supplier_ids = []; $customer_ids = []; foreach ($contact_defs as $c) { $sth = $pdo2->prepare("SELECT id FROM md_contact WHERE company_id = :c AND contact_name = :n LIMIT 1"); $sth->execute([':c' => $company_id, ':n' => $c['name']]); $id = (int)($sth->fetchColumn() ?: 0); if ($id) { skip("Contact '{$c['name']}' already exists (id={$id})"); } else { dbTransaction($pdo2, function ($pdo2) use ($contactMgmt, $c, $contact_type_ids, $logging) { $contactMgmt->saveContact([ 'id' => 0, 'contact_name' => $c['name'], 'tax_id' => $c['tax'], 'organization' => $c['name'], 'branch' => 'สำนักงานใหญ่', 'contact_type' => $contact_type_ids[$c['type']], 'billing_address' => '99 Business Rd, Bangkok 10110, Thailand', 'shipping_location' => 'Head Office', 'shipping_address' => '99 Business Rd, Bangkok 10110, Thailand', 'remark' => '', 'status' => 1, ], $logging, ''); }); $sth = $pdo2->prepare("SELECT id FROM md_contact WHERE company_id = :c AND contact_name = :n LIMIT 1"); $sth->execute([':c' => $company_id, ':n' => $c['name']]); $id = (int)$sth->fetchColumn(); ok("Created contact '{$c['name']}' ({$c['type']}, id={$id})"); } if ($c['type'] === 'Supplier') { $supplier_ids[] = $id; } else { $customer_ids[] = $id; } } // ───────────────────────────────────────────────────────────────────────────── // 5. Opening stock — receive every product into Main Warehouse bins, approved // ───────────────────────────────────────────────────────────────────────────── echo "\n--- Opening stock (Main Warehouse) ---\n"; $stockMgmt = new StockManager($pdo2, $company_id); $sth = $pdo2->prepare("SELECT COUNT(*) FROM `td_stock_{$main_wh_id}` WHERE company_id = :c AND type = 'in'"); $sth->execute([':c' => $company_id]); if ((int)$sth->fetchColumn() > 0) { skip("Opening stock already exists in Main Warehouse — skipping stock-in step"); } else { $bin_index = 1; foreach ($product_defs as $p) { $bin = 'A-' . $bin_index; $bin_index++; $supplier_id = $supplier_ids[array_rand($supplier_ids)]; $uuid = bin2hex(random_bytes(16)); dbTransaction($pdo2, function ($pdo2) use ($stockMgmt, $whMgmt, $main_wh_id, $p, $bin, $supplier_id, $logging, $uuid) { $stock_id = $stockMgmt->saveStockIn([ 'id' => 0, 'warehouse' => $main_wh_id, 'product_sku' => $p['sku'], 'quantity' => $p['qty'], 'price' => $p['cost'], 'zone' => $bin, 'aisle' => $bin, 'bin' => $bin, 'contact_id' => $supplier_id, 'description' => 'Opening stock (demo seed)', ], $logging, $uuid); $stockMgmt->approveStock($stock_id, $main_wh_id, 'in', $whMgmt); }); ok("Received {$p['qty']} {$p['uom']} of {$p['sku']} into bin {$bin}"); } } // ───────────────────────────────────────────────────────────────────────────── // 6. Restock — later replenishment receipts from suppliers, into fresh bins // (separate batch from opening stock, so the Stock In list shows more than // one receiving event). Bin is resolved dynamically (first free bin in the // target warehouse) rather than hardcoded, so this stays correct regardless // of which bins earlier runs / other seed steps have already taken. // ───────────────────────────────────────────────────────────────────────────── echo "\n--- Restock (additional stock-in) ---\n"; $restock_marker = 'Restock (demo seed)'; $restock_defs = [ ['warehouse' => $main_wh_id, 'warehouse_name' => 'Main Warehouse', 'sku' => 'EL-001', 'qty' => 80], ['warehouse' => $main_wh_id, 'warehouse_name' => 'Main Warehouse', 'sku' => 'OF-001', 'qty' => 200], ['warehouse' => $bangna_wh_id, 'warehouse_name' => 'Bangna Distribution Center', 'sku' => 'BV-001', 'qty' => 120], ['warehouse' => $main_wh_id, 'warehouse_name' => 'Main Warehouse', 'sku' => 'EL-002', 'qty' => 100], ['warehouse' => $main_wh_id, 'warehouse_name' => 'Main Warehouse', 'sku' => 'PK-001', 'qty' => 150], ['warehouse' => $bangna_wh_id, 'warehouse_name' => 'Bangna Distribution Center', 'sku' => 'BV-002', 'qty' => 60], ['warehouse' => $bangna_wh_id, 'warehouse_name' => 'Bangna Distribution Center', 'sku' => 'OF-002', 'qty' => 90], ]; foreach ($restock_defs as $r) { $p = findProductDef($product_defs, $r['sku']); $warehouse_id = $r['warehouse']; $sth = $pdo2->prepare( "SELECT COUNT(*) FROM `td_stock_{$warehouse_id}` WHERE company_id = :c AND type = 'in' AND description = :d AND product_sku = :s" ); $sth->execute([':c' => $company_id, ':d' => $restock_marker, ':s' => $r['sku']]); if ((int)$sth->fetchColumn() > 0) { skip("Restock of {$r['sku']} into '{$r['warehouse_name']}' already exists"); continue; } $bin = findFreeBin($pdo2, $company_id, $warehouse_id); $supplier_id = $supplier_ids[array_rand($supplier_ids)]; $uuid = bin2hex(random_bytes(16)); dbTransaction($pdo2, function ($pdo2) use ($stockMgmt, $whMgmt, $warehouse_id, $p, $r, $bin, $supplier_id, $logging, $uuid, $restock_marker) { $stock_id = $stockMgmt->saveStockIn([ 'id' => 0, 'warehouse' => $warehouse_id, 'product_sku' => $r['sku'], 'quantity' => $r['qty'], 'price' => $p['cost'], 'zone' => $bin, 'aisle' => $bin, 'bin' => $bin, 'contact_id' => $supplier_id, 'description' => $restock_marker, ], $logging, $uuid); $stockMgmt->approveStock($stock_id, $warehouse_id, 'in', $whMgmt); }); ok("Received {$r['qty']} {$p['uom']} of {$r['sku']} into bin {$bin} ({$r['warehouse_name']})"); } // ───────────────────────────────────────────────────────────────────────────── // 7. Stock-out — ship product out to customers. // // saveStockOut() always takes the *entire* quantity sitting in the source // bin (there is no partial-out parameter), so to produce a stock-out of a // specific, controlled quantity we first receive a small dedicated batch // into a fresh bin, approve it, then immediately move that whole batch out // again. This leaves the opening-stock / restock balances untouched while // still creating a standalone, realistic outbound movement. // ───────────────────────────────────────────────────────────────────────────── echo "\n--- Stock-out (Main Warehouse) ---\n"; $dispatch_in_marker = 'Batch for dispatch (demo seed)'; $dispatch_out_marker = 'Direct dispatch (demo seed)'; $stockout_defs = [ ['sku' => 'PK-003', 'qty' => 25, 'customer' => 'Siam Retail Co., Ltd.'], ['sku' => 'OF-003', 'qty' => 15, 'customer' => 'Bangkok Mart'], ['sku' => 'EL-004', 'qty' => 10, 'customer' => 'Chao Phraya Trading'], ['sku' => 'BV-002', 'qty' => 20, 'customer' => 'Northern Distribution Partners'], ['sku' => 'PK-001', 'qty' => 40, 'customer' => 'Siam Retail Co., Ltd.'], ['sku' => 'EL-002', 'qty' => 30, 'customer' => 'Bangkok Mart'], ]; foreach ($stockout_defs as $so) { $p = findProductDef($product_defs, $so['sku']); $sth = $pdo2->prepare( "SELECT COUNT(*) FROM `td_stock_{$main_wh_id}` WHERE company_id = :c AND type = 'out' AND description = :d AND product_sku = :s" ); $sth->execute([':c' => $company_id, ':d' => $dispatch_out_marker, ':s' => $so['sku']]); if ((int)$sth->fetchColumn() > 0) { skip("Dispatch of {$so['sku']} already exists"); continue; } $sth = $pdo2->prepare("SELECT id FROM md_contact WHERE company_id = :c AND contact_name = :n LIMIT 1"); $sth->execute([':c' => $company_id, ':n' => $so['customer']]); $customer_id = (int)$sth->fetchColumn(); $sku = $so['sku']; $qty = $so['qty']; $cost = $p['cost']; $uom = $p['uom']; // The batch is bought in from a supplier; only the stock-out goes to the customer. $supplier_id = $supplier_ids[0]; dbTransaction($pdo2, function ($pdo2) use ( $stockMgmt, $whMgmt, $company_id, $main_wh_id, $sku, $qty, $cost, $customer_id, $supplier_id, $logging, $dispatch_in_marker, $dispatch_out_marker ) { $bin = findFreeBin($pdo2, $company_id, $main_wh_id); $in_uuid = bin2hex(random_bytes(16)); $stock_id = $stockMgmt->saveStockIn([ 'id' => 0, 'warehouse' => $main_wh_id, 'product_sku' => $sku, 'quantity' => $qty, 'price' => $cost, 'zone' => $bin, 'aisle' => $bin, 'bin' => $bin, 'contact_id' => $supplier_id, 'description' => $dispatch_in_marker, ], $logging, $in_uuid); $stockMgmt->approveStock($stock_id, $main_wh_id, 'in', $whMgmt); $out_uuid = bin2hex(random_bytes(16)); $out_id = $stockMgmt->saveStockOut([ 'id' => 0, 'warehouse' => $main_wh_id, 'product_sku' => $sku, 'zone' => $bin, 'aisle' => $bin, 'bin' => $bin, 'contact_id' => $customer_id, 'description' => $dispatch_out_marker, ], $logging, $out_uuid); $stockMgmt->approveStock($out_id, $main_wh_id, 'out', $whMgmt); }); ok("Shipped {$qty} {$uom} of {$sku} to {$so['customer']}"); } // ───────────────────────────────────────────────────────────────────────────── // 8. Stock-transfer — move product from Main Warehouse to Bangna Distribution // Center. Same batch-then-move approach as stock-out above: receive a // dedicated small batch into a fresh Main Warehouse bin, then transfer that // whole batch to a fresh Bangna bin, so opening-stock / restock balances // in Main Warehouse are left untouched. // ───────────────────────────────────────────────────────────────────────────── echo "\n--- Stock-transfer (Main Warehouse -> Bangna Distribution Center) ---\n"; $transfer_in_marker = 'Batch for transfer (demo seed)'; $transfer_out_marker = 'Transfer to Bangna (demo seed)'; $transfer_defs = [ ['sku' => 'BV-001', 'qty' => 30], ['sku' => 'PK-002', 'qty' => 12], ['sku' => 'OF-004', 'qty' => 20], ['sku' => 'EL-003', 'qty' => 15], ['sku' => 'OF-002', 'qty' => 25], ['sku' => 'PK-001', 'qty' => 50], ]; foreach ($transfer_defs as $t) { $p = findProductDef($product_defs, $t['sku']); $sth = $pdo2->prepare( "SELECT COUNT(*) FROM `td_stock_{$main_wh_id}` WHERE company_id = :c AND type = 'transfer' AND description = :d AND product_sku = :s AND `out` > 0" ); $sth->execute([':c' => $company_id, ':d' => $transfer_out_marker, ':s' => $t['sku']]); if ((int)$sth->fetchColumn() > 0) { skip("Transfer of {$t['sku']} to Bangna Distribution Center already exists"); continue; } $sku = $t['sku']; $qty = $t['qty']; $cost = $p['cost']; $uom = $p['uom']; dbTransaction($pdo2, function ($pdo2) use ( $stockMgmt, $whMgmt, $company_id, $main_wh_id, $bangna_wh_id, $sku, $qty, $cost, $logging, $transfer_in_marker, $transfer_out_marker ) { $from_bin = findFreeBin($pdo2, $company_id, $main_wh_id); $in_uuid = bin2hex(random_bytes(16)); $stock_id = $stockMgmt->saveStockIn([ 'id' => 0, 'warehouse' => $main_wh_id, 'product_sku' => $sku, 'quantity' => $qty, 'price' => $cost, 'zone' => $from_bin, 'aisle' => $from_bin, 'bin' => $from_bin, 'contact_id' => 0, 'description' => $transfer_in_marker, ], $logging, $in_uuid); $stockMgmt->approveStock($stock_id, $main_wh_id, 'in', $whMgmt); $to_bin = findFreeBin($pdo2, $company_id, $bangna_wh_id); $transfer_uuid = bin2hex(random_bytes(16)); $transfer_id = $stockMgmt->saveStockTransfer([ 'id' => 0, 'warehouse_from' => $main_wh_id, 'zone_from' => $from_bin, 'aisle_from' => $from_bin, 'bin_from' => $from_bin, 'warehouse_to' => $bangna_wh_id, 'zone_to' => $to_bin, 'aisle_to' => $to_bin, 'bin_to' => $to_bin, 'product_sku' => $sku, 'contact_id' => 0, 'description' => $transfer_out_marker, ], $logging, $transfer_uuid); $stockMgmt->approveStock($transfer_id, $main_wh_id, 'transfer', $whMgmt); }); ok("Transferred {$qty} {$uom} of {$sku} from Main Warehouse to Bangna Distribution Center"); } // ───────────────────────────────────────────────────────────────────────────── echo "\n\033[1m=== Done ===\033[0m\n\n"; echo "Log in at /login/index.php with:\n"; echo " Username: {$demo_username}\n"; echo " Password: {$demo_password}\n"; echo " (No OTP step — the demo company has no SMTP configured.)\n\n";