Skip to content

Commit b96079b

Browse files
Avatarsiaclaude
andcommitted
feat(woocommerce): use products/batch endpoints for stock sync
Previously, stock sync ran two HTTP requests per article (SKU lookup + update). For n articles this was 2n roundtrips; at 1000 articles that is 2000 requests, easily tripping hoster rate limits and aborting the sync partway through. Switch to the official WC REST v3 batch endpoints: - Collect all SKUs up front, resolve them in one or a few products?sku=<csv>&per_page=100 lookups (map sku -> id). - Send stock updates in chunks of up to 100 items via POST products/batch. - Variations go through POST products/{parent}/variations/batch, grouped per parent product. Partial errors in a batch response are logged per SKU without aborting the rest of the sync. At 1000 articles this reduces request count from 2000 to roughly 15-20. WCClient::post() accepts array data and JSON-encodes it directly -- no new postBatch() helper needed. Closes OpenXE-org#263. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8bb1397 commit b96079b

File tree

1 file changed

+133
-26
lines changed

1 file changed

+133
-26
lines changed

www/pages/shopimporter_woocommerce.php

Lines changed: 133 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -495,8 +495,11 @@ public function ImportUpdateAuftrag()
495495
}
496496

497497
/**
498-
* This function syncs the current stock to the remote WooCommerce shop
499-
* @return int
498+
* This function syncs the current stock to the remote WooCommerce shop.
499+
* Uses WC REST v3 batch endpoints to reduce HTTP round-trips from 2n to
500+
* roughly ceil(n/100) + ceil(n/100) requests.
501+
*
502+
* @return int Number of articles successfully synced
500503
* @throws WCHttpClientException
501504
*/
502505
public function ImportSendListLager()
@@ -505,8 +508,12 @@ public function ImportSendListLager()
505508
$anzahl = 0;
506509
$ctmp = (!empty($tmp) ? count($tmp) : 0);
507510

511+
// --- Step 1: Collect all SKUs and compute desired stock params ---
512+
513+
// $pendingUpdates: sku => ['lageranzahl' => int, 'status' => string]
514+
$pendingUpdates = [];
515+
508516
for ($i = 0; $i < $ctmp; $i++) {
509-
// Get important values from input data
510517
$artikel = $tmp[$i]['artikel'];
511518
if ($artikel === 'ignore') {
512519
continue;
@@ -520,52 +527,152 @@ public function ImportSendListLager()
520527
$inaktiv = $tmp[$i]['inaktiv'];
521528
$status = 'publish';
522529

523-
// Do some computations, sanitize input
524-
525530
if ($pseudolager !== '') {
526531
$lageranzahl = $pseudolager;
527532
}
528-
529533
if ($tmp[$i]['ausverkauft']) {
530534
$lageranzahl = 0;
531535
}
532-
533536
if ($inaktiv) {
534537
$status = 'private';
535538
}
536539

537-
// get the product id that WooCommerce uses to represent the current article
538-
$remoteIdInformation = $this->getShopIdBySKU($nummer);
540+
$pendingUpdates[$nummer] = [
541+
'lageranzahl' => $lageranzahl,
542+
'status' => $status,
543+
];
544+
}
545+
546+
if (empty($pendingUpdates)) {
547+
return 0;
548+
}
539549

540-
if (empty($remoteIdInformation['id'])) {
541-
// The online shop doesnt know this article, write to log and continue with next product
550+
// --- Step 2: Bulk-resolve SKUs to WC product IDs ---
551+
// WC REST v3 accepts a comma-separated list in the ?sku= parameter.
552+
// We fetch in chunks of 100 to stay within per_page limits.
542553

543-
$this->logger->error("WooCommerce Artikel $nummer wurde im Online-Shop nicht gefunden! Falsche Artikelnummer im Shop hinterlegt?");
554+
// $skuMap: sku => ['id' => int, 'parent' => int, 'isvariant' => bool]
555+
$skuMap = [];
556+
$skuChunks = array_chunk(array_keys($pendingUpdates), 100);
557+
558+
foreach ($skuChunks as $skuChunk) {
559+
$skuCsv = implode(',', $skuChunk);
560+
$products = $this->client->get('products', [
561+
'sku' => $skuCsv,
562+
'per_page' => 100,
563+
]);
564+
if (!is_array($products)) {
544565
continue;
545566
}
567+
foreach ($products as $product) {
568+
if (!isset($product->sku)) {
569+
continue;
570+
}
571+
$skuMap[$product->sku] = [
572+
'id' => $product->id,
573+
'parent' => $product->parent_id,
574+
'isvariant' => !empty($product->parent_id),
575+
];
576+
}
577+
}
578+
579+
// --- Step 3: Split into simple products and variations ---
580+
// simpleItems: list of batch-update items for POST products/batch
581+
// variationItems: parent_id => list of batch-update items for POST products/{parent}/variations/batch
546582

547-
// Sync settings to online store
548-
$updateProductParams = [
583+
$simpleItems = [];
584+
$variationItems = [];
585+
586+
foreach ($pendingUpdates as $sku => $params) {
587+
if (!isset($skuMap[$sku])) {
588+
$this->logger->error(
589+
"WooCommerce Artikel $sku wurde im Online-Shop nicht gefunden! Falsche Artikelnummer im Shop hinterlegt?"
590+
);
591+
continue;
592+
}
593+
594+
$info = $skuMap[$sku];
595+
$item = [
596+
'id' => $info['id'],
549597
'manage_stock' => true,
550-
'status' => $status,
551-
'stock_quantity' => $lageranzahl
552-
// WooCommerce doesnt have a standard property for the other values, we're ignoring them
598+
'stock_quantity' => $params['lageranzahl'],
599+
'status' => $params['status'],
553600
];
554-
if ($remoteIdInformation['isvariant']) {
555-
$result = $this->client->put('products/' . $remoteIdInformation['parent'] . '/variations/' . $remoteIdInformation['id'], $updateProductParams);
601+
602+
if ($info['isvariant']) {
603+
$variationItems[$info['parent']][] = $item;
556604
} else {
557-
$result = $this->client->put('products/' . $remoteIdInformation['id'], $updateProductParams);
605+
$simpleItems[] = $item;
558606
}
607+
}
608+
609+
// --- Step 4: Send batch updates in chunks of 100, handle partial errors ---
610+
611+
// Simple products
612+
foreach (array_chunk($simpleItems, 100) as $chunk) {
613+
$response = $this->client->post('products/batch', ['update' => $chunk]);
614+
$anzahl += $this->processBatchResponse($response, 'products/batch');
615+
}
616+
617+
// Variations (one batch endpoint per parent product)
618+
foreach ($variationItems as $parentId => $items) {
619+
foreach (array_chunk($items, 100) as $chunk) {
620+
$endpoint = 'products/' . $parentId . '/variations/batch';
621+
$response = $this->client->post($endpoint, ['update' => $chunk]);
622+
$anzahl += $this->processBatchResponse($response, $endpoint);
623+
}
624+
}
559625

626+
return $anzahl;
627+
}
628+
629+
/**
630+
* Evaluates a WC batch response object, logs per-item errors, and returns
631+
* the count of successfully updated items.
632+
*
633+
* @param object $response Decoded JSON response from the batch endpoint.
634+
* @param string $endpoint Endpoint label used in log messages.
635+
* @return int Number of items reported as updated without error.
636+
*/
637+
private function processBatchResponse($response, $endpoint)
638+
{
639+
$successCount = 0;
640+
641+
if (!is_object($response) && !is_array($response)) {
642+
$this->logger->error("WooCommerce Batch-Response ungueltig fuer $endpoint");
643+
return 0;
644+
}
645+
646+
// Successful updates are in response->update
647+
$updated = is_object($response) ? ($response->update ?? []) : [];
648+
foreach ($updated as $item) {
649+
// WC embeds per-item errors inside the update array when an item fails
650+
if (isset($item->error)) {
651+
$code = $item->error->code ?? '';
652+
$message = $item->error->message ?? '';
653+
$this->logger->error(
654+
"WooCommerce Batch-Fehler ($endpoint) fuer ID {$item->id}: [$code] $message"
655+
);
656+
} else {
657+
$this->logger->error(
658+
"WooCommerce Lagerzahlenübertragung (Batch) fuer Artikel-ID {$item->id} erfolgreich",
659+
['endpoint' => $endpoint]
660+
);
661+
$successCount++;
662+
}
663+
}
664+
665+
// Top-level errors array (some WC versions use this)
666+
$errors = is_object($response) ? ($response->errors ?? []) : [];
667+
foreach ($errors as $err) {
668+
$code = $err->code ?? '';
669+
$message = $err->message ?? '';
560670
$this->logger->error(
561-
"WooCommerce Lagerzahlenübertragung für Artikel: $nummer / $remoteIdInformation[id] - Anzahl: $lageranzahl",
562-
[
563-
'result' => $result
564-
]
671+
"WooCommerce Batch-Fehler ($endpoint): [$code] $message"
565672
);
566-
$anzahl++;
567673
}
568-
return $anzahl;
674+
675+
return $successCount;
569676
}
570677

571678
public function ImportStorniereAuftrag()

0 commit comments

Comments
 (0)