PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.21
ووسلام – همگام سازی ووکامرس و باسلام v1.10.21
1.10.21 1.10.19 1.10.20 1.10.18 1.10.17 1.10.15 1.10.14 1.10.13 1.10.12 1.10.10 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.2 1.9.1 1.9.0 1.8.8 1.8.5 All 54 releases
sync-basalam / includes / Services / Products / UpdateProductVariationsService.php

UpdateProductVariationsService.php in ووسلام – همگام سازی ووکامرس و باسلام 1.10.21, at includes/Services/Products/UpdateProductVariationsService.php

159 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SyncBasalam\Services\Products;
4
5 use SyncBasalam\Config\Endpoints;
6 use SyncBasalam\Jobs\Exceptions\NonRetryableException;
7 use SyncBasalam\Jobs\Exceptions\RetryableException;
8 use SyncBasalam\Jobs\Exceptions\StaleVariationException;
9 use SyncBasalam\Logger\Logger;
10 use SyncBasalam\Services\ApiServiceManager;
11
12 defined('ABSPATH') || exit;
13
14 class UpdateProductVariationsService
15 {
16 /** Variant fields that are sent to the single-variation endpoint. */
17 private const VARIATION_FIELDS = ['primary_price', 'stock', 'sku'];
18
19 private $apiservice;
20
21 public function __construct($apiservice = null)
22 {
23 $this->apiservice = $apiservice ?: syncBasalamContainer()->get(ApiServiceManager::class);
24 }
25
26 /**
27 * True when every variant already has a Basalam variation id, so each one can be
28 * updated with its own request instead of being sent inside the product payload.
29 */
30 public static function allVariantsHaveBasalamId(array $variants): bool
31 {
32 if (empty($variants)) return false;
33
34 foreach ($variants as $variant) {
35 if (empty($variant['id'])) return false;
36 }
37
38 return true;
39 }
40
41 /**
42 * Sends one PATCH request per variation.
43 *
44 * @return array{updated: int, skipped: int, failed: int}
45 */
46 public function updateVariations($basalamProductId, array $variants, $productId = null): array
47 {
48 $updated = 0;
49 $skipped = 0;
50 $failed = 0;
51 $firstError = null;
52
53 foreach ($variants as $variant) {
54 $data = array_intersect_key($variant, array_flip(self::VARIATION_FIELDS));
55 $data = array_filter($data, fn($value) => $value !== null);
56
57 if (empty($data)) {
58 $skipped++;
59 continue;
60 }
61
62 $url = sprintf(Endpoints::PRODUCT_VARIATION_UPDATE, $basalamProductId, $variant['id']);
63
64 try {
65 $this->sendVariationUpdate($url, $data);
66 $updated++;
67 } catch (RetryableException $e) {
68 throw $e;
69 } catch (NonRetryableException $e) {
70 // A missing remote variation means the local mapping is stale,
71 // not that the whole product is missing. Abort this per-variant
72 // pass immediately so the caller can rebuild all mappings in a
73 // single product update.
74 if ((int) $e->getCode() === 404) {
75 throw new StaleVariationException($basalamProductId, $variant['id'], $e);
76 }
77
78 $failed++;
79 $firstError = $firstError ?: $e->getMessage();
80
81 $this->logVariationError($e, $productId, $basalamProductId, $variant['id']);
82 } catch (\Exception $e) {
83 $failed++;
84 $firstError = $firstError ?: $e->getMessage();
85
86 $this->logVariationError($e, $productId, $basalamProductId, $variant['id']);
87 }
88 }
89
90 // Every variation failed with a permanent error: report it instead of pretending success.
91 if ($updated === 0 && $failed > 0) {
92 throw NonRetryableException::permanent(esc_html('بروزرسانی �
93 تغیرهای �
94 حصول نا�
95 وفق بود: ' . $firstError));
96 }
97
98 return ['updated' => $updated, 'skipped' => $skipped, 'failed' => $failed];
99 }
100
101 private function logVariationError(\Throwable $error, $productId, $basalamProductId, $basalamVariationId): void
102 {
103 Logger::error('خطا در بروزرسانی �
104 تغیر �
105 حصول در باسلا�
106 : ' . $error->getMessage(), [
107 'operation' => 'بروزرسانی �
108 تغیر �
109 حصول',
110 'product_id' => $productId,
111 'basalam_product_id' => $basalamProductId,
112 'basalam_variation_id' => $basalamVariationId,
113 ]);
114 }
115
116 private function sendVariationUpdate(string $url, array $data): void
117 {
118 $skuRetry = false;
119
120 while (true) {
121 try {
122 $response = $this->apiservice->patch($url, $data);
123 } catch (RetryableException $e) {
124 if ($this->retryWithoutDuplicateSku($data, $e, $skuRetry)) continue;
125
126 throw $e;
127 } catch (\Exception $e) {
128 if ($this->retryWithoutDuplicateSku($data, $e, $skuRetry)) continue;
129
130 throw $e;
131 }
132
133 if ($this->retryWithoutDuplicateSku($data, $response, $skuRetry)) continue;
134
135 // API adapters normally throw client errors, but keep the fallback
136 // bounded if an adapter returns the error response directly.
137 if (ProductSkuRetry::isDuplicateSkuError($response)) {
138 throw NonRetryableException::permanent('SKU �
139 تغیر �
140 حصول در باسلا�
141 تکراری است.');
142 }
143
144 return;
145 }
146 }
147
148 private function retryWithoutDuplicateSku(array &$data, $error, bool &$retried): bool
149 {
150 if ($retried || !ProductSkuRetry::hasSku($data)) return false;
151 if (!ProductSkuRetry::isDuplicateSkuError($error)) return false;
152
153 $data = ProductSkuRetry::withoutSkus($data);
154 $retried = true;
155
156 return true;
157 }
158 }
159