MigrateReviewJob.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. <?php
  2. namespace App\Jobs;
  3. use Illuminate\Bus\Queueable;
  4. use Illuminate\Contracts\Queue\ShouldQueue;
  5. use Illuminate\Foundation\Bus\Dispatchable;
  6. use Illuminate\Queue\InteractsWithQueue;
  7. use Illuminate\Queue\SerializesModels;
  8. use Illuminate\Support\Facades\DB;
  9. use Illuminate\Support\Facades\Log;
  10. /**
  11. * Processes a single batch of Magento reviews and writes them into the
  12. * Bagisto product_reviews table.
  13. *
  14. * Each job receives an array of pre-fetched review rows so no remote DB
  15. * connection is held across queue serialization/unserialization.
  16. */
  17. class MigrateReviewJob implements ShouldQueue
  18. {
  19. use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
  20. /** Retry up to 3 times before marking as failed. */
  21. public int $tries = 3;
  22. /** Allow 5 minutes per job for large batches. */
  23. public int $timeout = 300;
  24. /**
  25. * @param array<int, array<string, mixed>> $reviews
  26. * Each element contains:
  27. * review_id, created_at, magento_product_id, status_id,
  28. * title, comment, name, customer_email, avg_rating
  29. * @param array<int, int> $productIdMap magento_product_id → bagisto product_id
  30. * @param array<string, int> $customerEmailMap email → bagisto customer_id
  31. */
  32. public function __construct(
  33. private readonly array $reviews,
  34. private readonly array $productIdMap,
  35. private readonly array $customerEmailMap,
  36. ) {}
  37. public function handle(): void
  38. {
  39. $statusMap = [
  40. 1 => 'approved',
  41. 2 => 'pending',
  42. 3 => 'disapproved',
  43. ];
  44. $now = now();
  45. $inserted = 0;
  46. $skipped = 0;
  47. foreach ($this->reviews as $row) {
  48. $bagistoProductId = $this->productIdMap[$row['magento_product_id']] ?? null;
  49. if ($bagistoProductId === null) {
  50. Log::warning('MigrateReviewJob: no Bagisto product for Magento product_id='.$row['magento_product_id'].', review_id='.$row['review_id'].'. Skipping.');
  51. $skipped++;
  52. continue;
  53. }
  54. // Deduplicate: skip if already migrated (keyed on migrated_from_asteria_id).
  55. $exists = DB::table('product_reviews')
  56. ->where('migrated_from_asteria_id', $row['review_id'])
  57. ->exists();
  58. if ($exists) {
  59. $skipped++;
  60. continue;
  61. }
  62. $rating = $this->normaliseRating($row['avg_rating']);
  63. $status = $statusMap[$row['status_id']] ?? 'pending';
  64. $customerId = $row['customer_email']
  65. ? ($this->customerEmailMap[strtolower($row['customer_email'])] ?? null)
  66. : null;
  67. $title = $this->truncate((string) ($row['title'] ?? ''), 255) ?: '(no title)';
  68. $comment = $this->truncate((string) ($row['comment'] ?? ''), 65535);
  69. $name = $this->truncate((string) ($row['name'] ?? ''), 255) ?: 'Guest';
  70. DB::table('product_reviews')->insert([
  71. 'title' => $title,
  72. 'comment' => $comment,
  73. 'rating' => $rating,
  74. 'status' => $status,
  75. 'product_id' => $bagistoProductId,
  76. 'customer_id' => $customerId,
  77. 'name' => $name,
  78. 'migrated_from_asteria_id' => $row['review_id'],
  79. 'created_at' => $row['created_at'] ?? $now,
  80. 'updated_at' => $now,
  81. ]);
  82. $newReviewId = DB::getPdo()->lastInsertId();
  83. // Download and store review images from review_media_image.url
  84. if (! empty($row['images'])) {
  85. $this->migrateImages((int) $newReviewId, $row['images']);
  86. }
  87. $inserted++;
  88. }
  89. Log::info("MigrateReviewJob: inserted={$inserted}, skipped={$skipped}");
  90. }
  91. /**
  92. * Store image URLs from Asteria directly as attachment paths.
  93. * Files will be uploaded to S3 in bulk separately.
  94. *
  95. * @param int $bagistoReviewId
  96. * @param list<string> $urls
  97. */
  98. private function migrateImages(int $bagistoReviewId, array $urls): void
  99. {
  100. foreach ($urls as $url) {
  101. if (empty($url)) {
  102. continue;
  103. }
  104. DB::table('product_review_attachments')->insert([
  105. 'review_id' => $bagistoReviewId,
  106. 'type' => 'image',
  107. 'mime_type' => null,
  108. 'path' => $url,
  109. ]);
  110. }
  111. }
  112. /**
  113. * Map Magento's average star value (1–5 float) to a Bagisto integer 1–5.
  114. * Falls back to 5 when null/zero (treat missing rating as best-case).
  115. */
  116. private function normaliseRating(mixed $avgRating): int
  117. {
  118. if ($avgRating === null || $avgRating == 0) {
  119. return 5;
  120. }
  121. return (int) max(1, min(5, round((float) $avgRating)));
  122. }
  123. private function truncate(string $value, int $maxLength): string
  124. {
  125. return mb_strlen($value) > $maxLength
  126. ? mb_substr($value, 0, $maxLength)
  127. : $value;
  128. }
  129. }