MigrateAsteriaOrdersCommandTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. <?php
  2. namespace Webkul\BagistoApi\Tests\Unit\Migration;
  3. use Illuminate\Support\Facades\Cache;
  4. use Illuminate\Support\Facades\DB;
  5. use Illuminate\Support\Facades\Schema;
  6. use Webkul\BagistoApi\Tests\BagistoApiTestCase;
  7. use Webkul\Core\Models\Channel;
  8. use Webkul\Customer\Models\Customer;
  9. use Webkul\Product\Models\Product;
  10. use Webkul\Sales\Models\Order;
  11. use Webkul\Sales\Models\OrderAddress;
  12. use Webkul\Sales\Models\OrderItem;
  13. use Webkul\Sales\Models\OrderPayment;
  14. class MigrateAsteriaOrdersCommandTest extends BagistoApiTestCase
  15. {
  16. private string $sqlitePath;
  17. private string $connection = 'asteria_test';
  18. private const MID = 881900000;
  19. public function setUp(): void
  20. {
  21. parent::setUp();
  22. if (! Schema::hasColumn('orders', 'migrated_from_asteria_id')) {
  23. $this->markTestSkipped('Run php artisan migrate to add Asteria order columns.');
  24. }
  25. $this->seedRequiredData();
  26. $this->forgetLeakedAsteriaOrders();
  27. Cache::forget('migrate_asteria_orders_last_id');
  28. $this->sqlitePath = sys_get_temp_dir().'/asteria_order_cmd_'.uniqid('', true).'.sqlite';
  29. touch($this->sqlitePath);
  30. config()->set('database.connections.'.$this->connection, [
  31. 'driver' => 'sqlite',
  32. 'database' => $this->sqlitePath,
  33. 'prefix' => '',
  34. 'foreign_key_constraints' => false,
  35. ]);
  36. DB::purge($this->connection);
  37. MagentoSchema::create($this->connection);
  38. }
  39. public function tearDown(): void
  40. {
  41. DB::purge($this->connection);
  42. if (isset($this->sqlitePath) && is_file($this->sqlitePath)) {
  43. @unlink($this->sqlitePath);
  44. }
  45. parent::tearDown();
  46. }
  47. public function test_it_migrates_an_order_with_items_addresses_and_payment(): void
  48. {
  49. $customer = $this->createCustomer([
  50. 'email' => 'mig-pk-jane@example.test',
  51. 'first_name' => 'Jane',
  52. 'last_name' => 'Doe',
  53. 'migrated_from_asteria_id' => self::MID + 10,
  54. ]);
  55. $product = $this->createSimpleProduct('AST-TEST-WIG-001');
  56. $this->seedCompleteMagentoOrder();
  57. $this->artisan('orders:migrate-asteria', [
  58. '--connection' => $this->connection,
  59. '--reset-progress' => true,
  60. ])->assertSuccessful();
  61. $order = Order::query()->where('increment_id', 'T881900010')->first();
  62. $this->assertNotNull($order);
  63. $this->assertSame(self::MID + 10, (int) $order->id);
  64. $this->assertSame(self::MID + 10, (int) $order->migrated_from_asteria_id);
  65. $this->assertSame((int) $order->id, (int) $order->migrated_from_asteria_id);
  66. $this->assertSame(Order::STATUS_COMPLETED, $order->status);
  67. $this->assertSame((int) $customer->id, (int) $order->customer_id);
  68. $this->assertSame(0, (int) $order->is_guest);
  69. $this->assertSame('mig-pk-jane@example.test', $order->customer_email);
  70. $this->assertEquals(110, (float) $order->grand_total);
  71. $this->assertEquals(100, (float) $order->sub_total);
  72. $this->assertEquals(10, (float) $order->shipping_amount);
  73. $this->assertSame('2021-06-01 12:00:00', $order->created_at?->format('Y-m-d H:i:s'));
  74. $item = OrderItem::query()->where('order_id', $order->id)->first();
  75. $this->assertNotNull($item);
  76. $this->assertSame('AST-TEST-WIG-001', $item->sku);
  77. $this->assertSame('Lace Wig', $item->name);
  78. $this->assertSame((int) $product->id, (int) $item->product_id);
  79. $this->assertSame('simple', $item->type);
  80. $this->assertEquals(100, (float) $item->price);
  81. $this->assertSame(101, (int) ($item->additional['asteria_item_id'] ?? 0));
  82. $billing = OrderAddress::query()
  83. ->where('order_id', $order->id)
  84. ->where('address_type', OrderAddress::ADDRESS_TYPE_BILLING)
  85. ->first();
  86. $this->assertNotNull($billing);
  87. $this->assertSame('123 Main St, Apt 4', $billing->address);
  88. $this->assertSame('Austin', $billing->city);
  89. $this->assertSame('US', $billing->country);
  90. $shipping = OrderAddress::query()
  91. ->where('order_id', $order->id)
  92. ->where('address_type', OrderAddress::ADDRESS_TYPE_SHIPPING)
  93. ->first();
  94. $this->assertNotNull($shipping);
  95. $this->assertSame('9 Oak Rd', $shipping->address);
  96. $payment = OrderPayment::query()->where('order_id', $order->id)->first();
  97. $this->assertNotNull($payment);
  98. $this->assertSame('paypal_standard', $payment->method);
  99. $this->assertSame('paypal_express', $payment->additional['magento_method'] ?? null);
  100. $this->assertSame('ABC123', $payment->additional['last_trans_id'] ?? null);
  101. }
  102. public function test_dry_run_does_not_write_orders(): void
  103. {
  104. MagentoSchema::seedOrder($this->connection, [
  105. 'entity_id' => self::MID + 10,
  106. 'increment_id' => 'T881900010',
  107. 'customer_email' => 'dry-run@example.com',
  108. ]);
  109. $before = Order::query()->count();
  110. $this->artisan('orders:migrate-asteria', [
  111. '--connection' => $this->connection,
  112. '--dry-run' => true,
  113. '--reset-progress' => true,
  114. ])->assertSuccessful();
  115. $this->assertSame($before, Order::query()->count());
  116. $this->assertNull(Order::query()->where('increment_id', 'T881900010')->first());
  117. }
  118. public function test_it_is_idempotent_on_a_second_run(): void
  119. {
  120. MagentoSchema::seedOrder($this->connection, [
  121. 'entity_id' => self::MID + 80,
  122. 'increment_id' => 'T881900080',
  123. 'customer_email' => 'mig-pk-once@example.test',
  124. 'items' => [
  125. ['item_id' => 801, 'sku' => 'ONCE-1', 'name' => 'Once'],
  126. ],
  127. 'addresses' => [
  128. ['entity_id' => 802, 'address_type' => 'billing'],
  129. ],
  130. 'payment' => [
  131. 'entity_id' => 803,
  132. 'method' => 'checkmo',
  133. ],
  134. ]);
  135. $options = [
  136. '--connection' => $this->connection,
  137. '--reset-progress' => true,
  138. ];
  139. $this->artisan('orders:migrate-asteria', $options)->assertSuccessful();
  140. $this->artisan('orders:migrate-asteria', $options)->assertSuccessful();
  141. $this->assertSame(1, Order::query()->where('increment_id', 'T881900080')->count());
  142. $order = Order::query()->where('increment_id', 'T881900080')->first();
  143. $this->assertSame(self::MID + 80, (int) $order->id);
  144. $this->assertSame(self::MID + 80, (int) $order->migrated_from_asteria_id);
  145. $this->assertSame(1, OrderItem::query()->where('order_id', $order->id)->count());
  146. $this->assertSame(1, OrderAddress::query()->where('order_id', $order->id)->count());
  147. $this->assertSame(1, OrderPayment::query()->where('order_id', $order->id)->count());
  148. $this->assertSame('moneytransfer', $order->payment->method);
  149. }
  150. public function test_it_links_a_customer_by_asteria_id(): void
  151. {
  152. $customer = $this->createCustomer([
  153. 'email' => 'mig-pk-linked@example.test',
  154. 'migrated_from_asteria_id' => self::MID + 44,
  155. ]);
  156. MagentoSchema::seedOrder($this->connection, [
  157. 'entity_id' => self::MID + 90,
  158. 'increment_id' => 'T881900090',
  159. 'customer_id' => self::MID + 44,
  160. 'customer_email' => 'mig-pk-other-address@example.test',
  161. 'items' => [
  162. ['item_id' => 901, 'sku' => 'LINK-1'],
  163. ],
  164. 'payment' => ['entity_id' => 903, 'method' => 'paypal_standard'],
  165. ]);
  166. $this->artisan('orders:migrate-asteria', [
  167. '--connection' => $this->connection,
  168. '--reset-progress' => true,
  169. ])->assertSuccessful();
  170. $order = Order::query()->where('increment_id', 'T881900090')->first();
  171. $this->assertNotNull($order);
  172. $this->assertSame((int) $customer->id, (int) $order->customer_id);
  173. $this->assertSame(0, (int) $order->is_guest);
  174. }
  175. public function test_it_creates_a_guest_order_when_the_customer_is_missing(): void
  176. {
  177. MagentoSchema::seedOrder($this->connection, [
  178. 'entity_id' => self::MID + 91,
  179. 'increment_id' => 'T881900091',
  180. 'customer_id' => self::MID + 9999,
  181. 'customer_email' => 'mig-pk-nobody@example.test',
  182. 'customer_firstname' => 'Guest',
  183. 'customer_lastname' => 'Buyer',
  184. 'customer_is_guest' => 1,
  185. 'items' => [
  186. ['item_id' => 911, 'sku' => 'GUEST-1'],
  187. ],
  188. 'payment' => ['entity_id' => 913, 'method' => 'cashondelivery'],
  189. ]);
  190. $this->artisan('orders:migrate-asteria', [
  191. '--connection' => $this->connection,
  192. '--reset-progress' => true,
  193. ])->assertSuccessful();
  194. $order = Order::query()->where('increment_id', 'T881900091')->first();
  195. $this->assertNotNull($order);
  196. $this->assertNull($order->customer_id);
  197. $this->assertSame(1, (int) $order->is_guest);
  198. $this->assertSame('mig-pk-nobody@example.test', $order->customer_email);
  199. $this->assertSame('Guest', $order->customer_first_name);
  200. $this->assertSame('cashondelivery', $order->payment->method);
  201. }
  202. public function test_it_imports_unmatched_sku_items_without_a_product_id(): void
  203. {
  204. MagentoSchema::seedOrder($this->connection, [
  205. 'entity_id' => self::MID + 92,
  206. 'increment_id' => 'T881900092',
  207. 'items' => [
  208. [
  209. 'item_id' => 921,
  210. 'sku' => 'MISSING-SKU',
  211. 'name' => 'Retired Product',
  212. 'product_type' => 'simple',
  213. ],
  214. ],
  215. 'payment' => ['entity_id' => 923, 'method' => 'checkmo'],
  216. ]);
  217. $this->artisan('orders:migrate-asteria', [
  218. '--connection' => $this->connection,
  219. '--reset-progress' => true,
  220. ])->assertSuccessful();
  221. $order = Order::query()->where('increment_id', 'T881900092')->first();
  222. $this->assertNotNull($order);
  223. $item = OrderItem::query()->where('order_id', $order->id)->first();
  224. $this->assertSame('MISSING-SKU', $item->sku);
  225. $this->assertSame('Retired Product', $item->name);
  226. $this->assertNull($item->product_id);
  227. $this->assertNull($item->product_type);
  228. $this->assertSame('simple', $item->type);
  229. }
  230. public function test_it_skips_when_increment_id_already_exists(): void
  231. {
  232. $channel = Channel::query()->first();
  233. $native = Order::factory()->create([
  234. 'increment_id' => 'T881900099',
  235. 'channel_id' => $channel?->id,
  236. 'channel_type' => Channel::class,
  237. 'customer_id' => null,
  238. 'is_guest' => 1,
  239. 'customer_email'=> 'native@example.com',
  240. ]);
  241. $bagistoId = (int) $native->id;
  242. MagentoSchema::seedOrder($this->connection, [
  243. 'entity_id' => self::MID + 99,
  244. 'increment_id' => 'T881900099',
  245. 'customer_email' => 'asteria@example.com',
  246. 'items' => [
  247. ['item_id' => 991, 'sku' => 'SKIP-1'],
  248. ],
  249. 'payment' => ['entity_id' => 993, 'method' => 'checkmo'],
  250. ]);
  251. $this->artisan('orders:migrate-asteria', [
  252. '--connection' => $this->connection,
  253. '--reset-progress' => true,
  254. ])->assertSuccessful();
  255. $this->assertSame(1, Order::query()->where('increment_id', 'T881900099')->count());
  256. $existing = Order::query()->where('increment_id', 'T881900099')->first();
  257. $this->assertNull($existing->migrated_from_asteria_id);
  258. $this->assertSame('native@example.com', $existing->customer_email);
  259. $this->assertSame($bagistoId, (int) $existing->id);
  260. }
  261. private function seedCompleteMagentoOrder(): void
  262. {
  263. MagentoSchema::seedOrder($this->connection, [
  264. 'entity_id' => self::MID + 10,
  265. 'increment_id' => 'T881900010',
  266. 'customer_id' => self::MID + 10,
  267. 'customer_email' => 'mig-pk-jane@example.test',
  268. 'customer_firstname' => 'Jane',
  269. 'customer_lastname' => 'Doe',
  270. 'status' => 'complete',
  271. 'state' => 'complete',
  272. 'grand_total' => 110,
  273. 'subtotal' => 100,
  274. 'shipping_amount' => 10,
  275. 'created_at' => '2021-06-01 12:00:00',
  276. 'items' => [
  277. [
  278. 'item_id' => 101,
  279. 'sku' => 'AST-TEST-WIG-001',
  280. 'name' => 'Lace Wig',
  281. 'qty_ordered'=> 1,
  282. 'price' => 100,
  283. 'row_total' => 100,
  284. ],
  285. ],
  286. 'addresses' => [
  287. [
  288. 'entity_id' => 201,
  289. 'address_type' => 'billing',
  290. 'firstname' => 'Jane',
  291. 'lastname' => 'Doe',
  292. 'street' => "123 Main St\nApt 4",
  293. 'city' => 'Austin',
  294. 'country_id' => 'US',
  295. ],
  296. [
  297. 'entity_id' => 202,
  298. 'address_type' => 'shipping',
  299. 'firstname' => 'Jane',
  300. 'lastname' => 'Doe',
  301. 'street' => '9 Oak Rd',
  302. 'city' => 'Dallas',
  303. 'country_id' => 'US',
  304. ],
  305. ],
  306. 'payment' => [
  307. 'entity_id' => 301,
  308. 'method' => 'paypal_express',
  309. 'last_trans_id' => 'ABC123',
  310. ],
  311. ]);
  312. }
  313. private function createSimpleProduct(string $sku): Product
  314. {
  315. $attributeFamilyId = (int) (DB::table('attribute_families')->value('id') ?? 1);
  316. return Product::factory()->create([
  317. 'sku' => $sku,
  318. 'type' => 'simple',
  319. 'attribute_family_id' => $attributeFamilyId,
  320. ]);
  321. }
  322. private function forgetLeakedAsteriaOrders(): void
  323. {
  324. $orderIds = Order::query()
  325. ->where('increment_id', 'like', 'T8819%')
  326. ->pluck('id');
  327. if ($orderIds->isNotEmpty()) {
  328. OrderItem::query()->whereIn('order_id', $orderIds)->delete();
  329. OrderAddress::query()->whereIn('order_id', $orderIds)->delete();
  330. OrderPayment::query()->whereIn('order_id', $orderIds)->delete();
  331. Order::query()->whereIn('id', $orderIds)->delete();
  332. }
  333. $customerIds = Customer::query()
  334. ->where('email', 'like', 'mig-pk-%')
  335. ->pluck('id');
  336. if ($customerIds->isNotEmpty()) {
  337. Customer::query()->whereIn('id', $customerIds)->delete();
  338. }
  339. Product::query()->where('sku', 'like', 'AST-TEST-%')->delete();
  340. }
  341. }