PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.20
ووسلام – همگام سازی ووکامرس و باسلام v1.10.20
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 1.8.6 All 53 releases
← All changes | includes/Services/Products/UpdateSingleProductService.php +112 -2 1.10.11.10.20 View file →
@@ -6,8 +6,9 @@
6 6 use SyncBasalam\Services\ApiServiceManager;
7 7 use SyncBasalam\Jobs\Exceptions\RetryableException;
8 8 use SyncBasalam\Jobs\Exceptions\NonRetryableException;
9 9 use SyncBasalam\Utilities\ProductMetaKey;
10 +use SyncBasalam\Services\VendorSyncPolicy;
10 11
11 12 defined('ABSPATH') || exit;
12 13
13 14 class UpdateSingleProductService
@@ -12,12 +13,14 @@
12 13
13 14 class UpdateSingleProductService
14 15 {
15 16 private $apiservice;
17 + private $variationsService;
16 18
17 19 public function __construct()
18 20 {
19 21 $this->apiservice = syncBasalamContainer()->get(ApiServiceManager::class);
22 + $this->variationsService = syncBasalamContainer()->get(UpdateProductVariationsService::class);
20 23 }
21 24
22 25 public function updateProductInBasalam($productData, $productId)
23 26 {
@@ -22,34 +25,72 @@
22 25 public function updateProductInBasalam($productData, $productId)
23 26 {
24 27 if (!get_post_type($productId) === 'product') throw NonRetryableException::invalidData('نوع post محصول نیست.');
25 28
29 + $vendorSyncPolicy = syncBasalamContainer()->get(VendorSyncPolicy::class);
30 + if (!$vendorSyncPolicy->canUpdate()) {
31 + throw NonRetryableException::invalidData($vendorSyncPolicy->getRestrictionMessage(false));
32 + }
33 +
26 34 $productData = apply_filters('sync_basalam_product_data_before_update', $productData, $productId);
35 + $productData = $vendorSyncPolicy->restrictUpdatePayload($productData, false);
27 36
28 37 do_action('sync_basalam_before_update_product_api', $productId, $productData);
29 38
30 39 $syncBasalamProductId = get_post_meta($productId, ProductMetaKey::basalamProductId(), true);
40 + ProductConnection::assertUnique($productId, $syncBasalamProductId);
31 41
42 + // Variable products whose variations are all connected to Basalam are updated with one
43 + // request per variation, so price and stock must not be part of the product payload.
44 + if ($this->shouldUpdateVariationsSeparately($productId, $productData)) {
45 + $this->variationsService->updateVariations($syncBasalamProductId, $productData['variants'], $productId);
46 +
47 + unset($productData['variants'], $productData['primary_price'], $productData['stock']);
48 +
49 + if (!$this->hasProductFieldsToUpdate($productData)) {
50 + return $this->finishUpdate($productId, [], 'متغیرهای محصول با موفقیت بروزرسانی شدند.');
51 + }
52 + }
32 53 $url = sprintf(Endpoints::PRODUCT_UPDATE, $syncBasalamProductId);
33 54
34 55 $maxDescriptionRetries = 3;
35 56 $descriptionRetry = 0;
57 + $skuRetry = false;
36 58
37 59 while (true) {
38 60 try {
39 61 $request = $this->apiservice->patch($url, $productData);
40 - break;
41 62 } catch (RetryableException $e) {
63 + if ($this->retryWithoutDuplicateSku($productData, $e, $skuRetry)) {
64 + continue;
65 + }
66 +
42 67 throw $e;
43 68 } catch (NonRetryableException $e) {
69 + if ($this->retryWithoutDuplicateSku($productData, $e, $skuRetry)) {
70 + continue;
71 + }
72 +
44 73 if ($descriptionRetry < $maxDescriptionRetries && $this->stripForbiddenDescription($e, $productData, $productId, $descriptionRetry)) {
45 74 $descriptionRetry++;
46 75 continue;
47 76 }
77 +
48 78 throw $e;
49 79 } catch (\Exception $e) {
80 + if ($this->retryWithoutDuplicateSku($productData, $e, $skuRetry)) {
81 + continue;
82 + }
83 +
50 84 throw new \Exception(esc_html('خطا در ارتباط با API باسلام: ' . $e->getMessage()));
51 85 }
86 +
87 + // Some API adapters return a non-2xx response instead of throwing it.
88 + if ($this->retryWithoutDuplicateSku($productData, $request, $skuRetry)) {
89 + continue;
90 + }
91 +
92 + break;
52 93 }
53 94
54 95 $body = $request['body'] ?? '';
55 96
@@ -75,8 +116,17 @@
75 116 }
76 117
77 118 if (is_wp_error($request)) throw NonRetryableException::permanent('خطایی در ارتباط با سرور رخ داد.');
78 119
120 + // Basalam may return a successful response with a stale/null product SKU
121 + // when another product field (most commonly a long description) is
122 + // present in the same patch. Send the SKU on its own when the response
123 + // does not contain the value we requested, so the product-level SKU is
124 + // not lost for variable products.
125 + // When the duplicate-SKU fallback was used, deliberately keep the
126 + // second request SKU-free; do not issue a follow-up SKU-only patch.
127 + if (!$skuRetry) $this->ensureProductSkuUpdated($url, $productData, $body);
128 +
79 129 $product = \wc_get_product($productId);
80 130 if ($product && $product->is_type('variable')) {
81 131 $variations = $product->get_children();
82 132 if (isset($body['variants'])) {
@@ -139,13 +189,18 @@
139 189 }
140 190 }
141 191 }
142 192
193 + return $this->finishUpdate($productId, $body, 'فرایند بروزرسانی محصول با موفقیت انجام شد.');
194 + }
195 +
196 + private function finishUpdate($productId, $body, string $message): array
197 + {
143 198 update_post_meta($productId, ProductMetaKey::basalamProductSyncStatus(), 'synced');
144 199
145 200 $result = [
146 201 'success' => true,
147 - 'message' => 'فرایند بروزرسانی محصول با موفقیت انجام شد.',
202 + 'message' => $message,
148 203 'status_code' => 200,
149 204 ];
150 205
151 206 do_action('sync_basalam_after_update_product_api', $productId, $body, $result);
@@ -152,8 +207,25 @@
152 207
153 208 return $result;
154 209 }
155 210
211 + private function shouldUpdateVariationsSeparately($productId, array $productData): bool
212 + {
213 + if (empty($productData['variants']) || !is_array($productData['variants'])) return false;
214 +
215 + $product = \wc_get_product($productId);
216 + if (!$product || !$product->is_type('variable')) return false;
217 +
218 + return UpdateProductVariationsService::allVariantsHaveBasalamId($productData['variants']);
219 + }
220 +
221 + private function hasProductFieldsToUpdate(array $productData): bool
222 + {
223 + $identifiers = ['id' => true, 'type' => true];
224 +
225 + return !empty(array_diff_key($productData, $identifiers));
226 + }
227 +
156 228 private function stripForbiddenDescription(NonRetryableException $e, array &$productData, int $productId, int $attempt): bool
157 229 {
158 230 if (!isset($productData['description']) || !is_string($productData['description'])) return false;
159 231
@@ -167,11 +239,30 @@
167 239
168 240 return true;
169 241 }
170 242
243 + private function retryWithoutDuplicateSku(array &$productData, $error, bool &$retried): bool
244 + {
245 + if ($retried || !ProductSkuRetry::hasSku($productData)) return false;
246 + if (!ProductSkuRetry::isDuplicateSkuError($error)) return false;
247 +
248 + $productData = ProductSkuRetry::withoutSkus($productData);
249 + $retried = true;
250 +
251 + return true;
252 + }
253 +
171 254 public function updateProductStatus($productId, $status)
172 255 {
256 + $vendorSyncPolicy = syncBasalamContainer()->get(VendorSyncPolicy::class);
257 + if (!$vendorSyncPolicy->canUpdateProductStatus()) {
258 + throw NonRetryableException::invalidData($vendorSyncPolicy->getRestrictionMessage(false));
259 + }
260 +
173 261 $syncBasalamProductId = get_post_meta($productId, ProductMetaKey::basalamProductId(), true);
262 +
263 + ProductConnection::assertUnique($productId, $syncBasalamProductId);
264 +
174 265 $url = sprintf(Endpoints::PRODUCT_UPDATE, $syncBasalamProductId);
175 266
176 267 $data = ["status" => $status];
177 268
@@ -204,6 +295,25 @@
204 295 return $result;
205 296 }
206 297
207 298 throw NonRetryableException::permanent("تغییر وضعیت محصول در باسلام ناموفق بود.");
299 + }
300 +
301 + private function ensureProductSkuUpdated(string $url, array $productData, $responseBody): void
302 + {
303 + if (!array_key_exists('sku', $productData) || $productData['sku'] === null) return;
304 +
305 + $requestedSku = (string) $productData['sku'];
306 + $responseSku = null;
307 +
308 + if (is_array($responseBody) && array_key_exists('sku', $responseBody)) {
309 + $responseSku = $responseBody['sku'];
310 + }
311 +
312 + if ($responseSku !== null && (string) $responseSku === $requestedSku) return;
313 +
314 + $skuRequest = $this->apiservice->patch($url, ['sku' => $productData['sku']]);
315 + if (!is_array($skuRequest) || (int) ($skuRequest['status_code'] ?? 0) !== 200) {
316 + throw NonRetryableException::permanent('بروزرسانی شناسه محصول (SKU) در باسلام ناموفق بود.');
317 + }
208 318 }
209 319 }