@@ -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