From 636b7f65820d96b66f9d7eafbd9b642a2299a649 Mon Sep 17 00:00:00 2001 From: August Miller Date: Thu, 8 Aug 2024 11:25:13 -0700 Subject: [PATCH 1/8] Revise handling of variant data to discover stock in the expected location --- src/elements/CommerceProduct.php | 69 +++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/src/elements/CommerceProduct.php b/src/elements/CommerceProduct.php index 0869ead0..f9ab05ff 100644 --- a/src/elements/CommerceProduct.php +++ b/src/elements/CommerceProduct.php @@ -21,6 +21,7 @@ use craft\feedme\services\Process; use craft\fields\Matrix; use craft\fields\Table; +use craft\helpers\ArrayHelper; use craft\helpers\Json; use DateTime; use Exception; @@ -480,31 +481,63 @@ private function _parseVariants($event): void private function _inventoryUpdate($event): void { + /** @var Product $product */ + $product = $event->element; + + // Index variants by SKU for lookup: + $variantsBySku = ArrayHelper::index($event->contentData['variants'], 'sku'); /** @var Commerce $commercePlugin */ $commercePlugin = Commerce::getInstance(); - $variants = $event->element->getVariants(); + $variants = $product->getVariants(); + // Queue up a changeset: $updateInventoryLevels = UpdateInventoryLevelCollection::make(); + foreach ($variants as $variant) { - if ($inventoryItem = $commercePlugin->getInventory()->getInventoryItemByPurchasable($variant)) { - /** @var InventoryLevel $firstInventoryLevel */ - $firstInventoryLevel = $commercePlugin->getInventory()->getInventoryLevelsForPurchasable($variant)->first(); - if ($firstInventoryLevel && $firstInventoryLevel->getInventoryLocation()) { - $feedData = $event->feedData; - $data = Json::decodeIfJson($event->feedData, true); - $stock = $data['stock'] ?? 0; - $updateInventoryLevels->push(new UpdateInventoryLevel([ - 'type' => \craft\commerce\enums\InventoryTransactionType::AVAILABLE->value, - 'updateAction' => \craft\commerce\enums\InventoryUpdateQuantityType::SET, - 'inventoryItem' => $inventoryItem, - 'inventoryLocation' => $firstInventoryLevel->getInventoryLocation(), - 'quantity' => $stock, - 'note' => '', - ]) - ); - } + // Do we have a node for this variant at all? + if (!isset($variantsBySku[$variant->sku])) { + continue; + } + + $stock = $variantsBySku[$variant->sku]['stock'] ?? null; + + // What if the `stock` key wasn't in the incoming data? + if (is_null($stock)) { + Plugin::error(sprintf('No stock value was present in the import data for %s.', $variant->sku)); + + continue; + } + + // Load InventoryItem model: + $inventoryItem = $commercePlugin->getInventory()->getInventoryItemByPurchasable($variant); + + if (!$inventoryItem) { + // Not tracking, never mind! + continue; + } + + /** @var InventoryLevel $firstInventoryLevel */ + $level = $commercePlugin->getInventory()->getInventoryLevelsForPurchasable($variant)->first(); + $location = $level->getInventoryLocation(); + + if (!$level || !$location) { + // Again, looks like there's nothing to track… + continue; } + + $update = new UpdateInventoryLevel([ + 'type' => \craft\commerce\enums\InventoryTransactionType::AVAILABLE->value, + 'updateAction' => \craft\commerce\enums\InventoryUpdateQuantityType::SET, + 'inventoryItem' => $inventoryItem, + 'inventoryLocation' => $location, + 'quantity' => $stock, + 'note' => sprintf('Imported via feed ID #%s', $event->feed->id), + ]); + + $updateInventoryLevels->push($update); + + Plugin::info(sprintf('Updating stock for the default inventory location for %s to %s.', $variant->sku, $stock)); } if ($updateInventoryLevels->count() > 0) { From 00fa0ab42902f97174f9dbf936a966890f7c0cb1 Mon Sep 17 00:00:00 2001 From: August Miller Date: Thu, 8 Aug 2024 11:45:02 -0700 Subject: [PATCH 2/8] Feed is an array at this point --- src/elements/CommerceProduct.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elements/CommerceProduct.php b/src/elements/CommerceProduct.php index f9ab05ff..d1774db1 100644 --- a/src/elements/CommerceProduct.php +++ b/src/elements/CommerceProduct.php @@ -532,7 +532,7 @@ private function _inventoryUpdate($event): void 'inventoryItem' => $inventoryItem, 'inventoryLocation' => $location, 'quantity' => $stock, - 'note' => sprintf('Imported via feed ID #%s', $event->feed->id), + 'note' => sprintf('Imported via feed ID #%s', $event->feed['id']), ]); $updateInventoryLevels->push($update); From 4351810a37918e65b162e9d49548d623537cb511 Mon Sep 17 00:00:00 2001 From: August Miller Date: Tue, 13 Aug 2024 10:12:53 -0700 Subject: [PATCH 3/8] `code` -> `code-group` fenced blocks --- docs/feature-tour/primary-element.md | 2 +- docs/guides/importing-assets.md | 2 +- docs/guides/importing-commerce-products.md | 2 +- docs/guides/importing-commerce-variants.md | 2 +- docs/guides/importing-entries.md | 2 +- docs/guides/importing-into-matrix.md | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/feature-tour/primary-element.md b/docs/feature-tour/primary-element.md index 546c2202..ec9d40ee 100644 --- a/docs/feature-tour/primary-element.md +++ b/docs/feature-tour/primary-element.md @@ -4,7 +4,7 @@ The primary element can be confusing at first, but its vitally important to ensu Take the following example: -::: code +::: code-group ```xml diff --git a/docs/guides/importing-assets.md b/docs/guides/importing-assets.md index 3214a98a..64e3d6e9 100644 --- a/docs/guides/importing-assets.md +++ b/docs/guides/importing-assets.md @@ -20,7 +20,7 @@ This will scan the folder and add images into Craft as assets. This data is what we’ll use for this guide: -::: code +::: code-group ```xml diff --git a/docs/guides/importing-commerce-products.md b/docs/guides/importing-commerce-products.md index a6b0344e..58b6b7de 100644 --- a/docs/guides/importing-commerce-products.md +++ b/docs/guides/importing-commerce-products.md @@ -10,7 +10,7 @@ Looking to import products with multiple Variants? Have a look at the [Importing The below data is what we'll use for this guide: -::: code +::: code-group ```xml diff --git a/docs/guides/importing-commerce-variants.md b/docs/guides/importing-commerce-variants.md index 2f188525..fd009afa 100644 --- a/docs/guides/importing-commerce-variants.md +++ b/docs/guides/importing-commerce-variants.md @@ -10,7 +10,7 @@ Looking to import products without Variants? Have a look at the [Importing Comme The below data is what we'll use for this guide: -::: code +::: code-group ```xml diff --git a/docs/guides/importing-entries.md b/docs/guides/importing-entries.md index db2a9c26..bd2f4e40 100644 --- a/docs/guides/importing-entries.md +++ b/docs/guides/importing-entries.md @@ -9,7 +9,7 @@ Looking for a Matrix example? Check out [Import into Matrix](importing-into-matr ### Example Feed Data The below data is what we'll use for this guide: -::: code +::: code-group ```xml diff --git a/docs/guides/importing-into-matrix.md b/docs/guides/importing-into-matrix.md index fee5d111..f17a0472 100644 --- a/docs/guides/importing-into-matrix.md +++ b/docs/guides/importing-into-matrix.md @@ -16,7 +16,7 @@ In this example, we'll be importing 2 Entries, which have a single Matrix field The below data is what we'll use for this guide: -::: code +::: code-group ```xml @@ -171,7 +171,7 @@ You'll notice we're using `Matrix` and `MatrixBlock` for the nodes that contain For example, you should **not** do: -::: code +::: code-group ```xml @@ -200,7 +200,7 @@ For example, you should **not** do: Instead, use the same named node to surround the content for each block: -::: code +::: code-group ```xml From 2000d6a68761a16c5943603e28733702203d3cb7 Mon Sep 17 00:00:00 2001 From: August Miller Date: Fri, 16 Aug 2024 16:16:43 -0700 Subject: [PATCH 4/8] Rearrange inventory checks, clarify comments --- src/elements/CommerceProduct.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/elements/CommerceProduct.php b/src/elements/CommerceProduct.php index d1774db1..9f702719 100644 --- a/src/elements/CommerceProduct.php +++ b/src/elements/CommerceProduct.php @@ -495,14 +495,20 @@ private function _inventoryUpdate($event): void $updateInventoryLevels = UpdateInventoryLevelCollection::make(); foreach ($variants as $variant) { - // Do we have a node for this variant at all? + // Is this SKU even present in our import data? if (!isset($variantsBySku[$variant->sku])) { continue; } + if (!$variant->inventoryTracked) { + Plugin::info(sprintf('Variant %s is not configured to track stock.', $variant->sku)); + + continue; + } + $stock = $variantsBySku[$variant->sku]['stock'] ?? null; - // What if the `stock` key wasn't in the incoming data? + // What if the `stock` key wasn't in the import data? if (is_null($stock)) { Plugin::error(sprintf('No stock value was present in the import data for %s.', $variant->sku)); @@ -512,11 +518,6 @@ private function _inventoryUpdate($event): void // Load InventoryItem model: $inventoryItem = $commercePlugin->getInventory()->getInventoryItemByPurchasable($variant); - if (!$inventoryItem) { - // Not tracking, never mind! - continue; - } - /** @var InventoryLevel $firstInventoryLevel */ $level = $commercePlugin->getInventory()->getInventoryLevelsForPurchasable($variant)->first(); $location = $level->getInventoryLocation(); From e19a39dfd375d74f19547d85cfd07102a3185003 Mon Sep 17 00:00:00 2001 From: Luke Holder Date: Tue, 3 Sep 2024 17:21:22 +0800 Subject: [PATCH 5/8] Fixed to inventory and saving --- src/elements/CommerceProduct.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/elements/CommerceProduct.php b/src/elements/CommerceProduct.php index 9f702719..6710b684 100644 --- a/src/elements/CommerceProduct.php +++ b/src/elements/CommerceProduct.php @@ -7,6 +7,7 @@ use Craft; use craft\base\ElementInterface; use craft\commerce\collections\UpdateInventoryLevelCollection; +use craft\commerce\elements\Product; use craft\commerce\elements\Product as ProductElement; use craft\commerce\elements\Variant as VariantElement; use craft\commerce\models\inventory\UpdateInventoryLevel; @@ -155,6 +156,13 @@ public function setModel($settings): ElementInterface $this->element->siteId = $siteId; } + $originalScenario = $this->element->getScenario(); + $this->element->setScenario(\craft\base\Element::SCENARIO_ESSENTIALS); + if (!Craft::$app->getDrafts()->saveElementAsDraft($this->element, null, null, null, false)) { + throw new Exception('Unable to create product element as unsaved'); + } + $this->element->setScenario($originalScenario); + return $this->element; } @@ -165,6 +173,11 @@ public function save($element, $settings): bool { $this->beforeSave($element, $settings); + if($this->element->getIsDraft()) { + $this->element->setDirtyAttributes(['variants']); + $this->element = Craft::$app->getDrafts()->applyDraft($this->element); + } + if (!Craft::$app->getElements()->saveElement($this->element, true, true, Hash::get($this->feed, 'updateSearchIndexes'))) { $errors = [$this->element->getErrors()]; @@ -275,6 +288,7 @@ private function _parseVariants($event): void $feed = $event->feed; $feedData = $event->feedData; $contentData = $event->contentData; + /** @var Product $element */ $element = $event->element; $variantMapping = Hash::get($feed, 'fieldMapping.variants'); @@ -432,7 +446,8 @@ private function _parseVariants($event): void $variants[$sku] = new VariantElement(); } - $variants[$sku]->product = $element; + $variants[$sku]->setOwner($element); + $variants[$sku]->setPrimaryOwner($element); // We are going to handle stock after the product and variants save $stock = null; From 7a99dcac69b400894f2ffab7e6197a973aa70e55 Mon Sep 17 00:00:00 2001 From: Iwona Just Date: Tue, 3 Sep 2024 11:52:19 +0100 Subject: [PATCH 6/8] only create unpublished draft of product if we didn't find a match --- src/elements/CommerceProduct.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/elements/CommerceProduct.php b/src/elements/CommerceProduct.php index 6710b684..223ebac4 100644 --- a/src/elements/CommerceProduct.php +++ b/src/elements/CommerceProduct.php @@ -91,6 +91,17 @@ public function init(): void // Hook into the process service on each step - we need to re-arrange the feed mapping Event::on(Process::class, Process::EVENT_STEP_BEFORE_PARSE_CONTENT, function(FeedProcessEvent $event) { if ($event->feed['elementType'] === ProductElement::class) { + // at this point we've matched existing elements; + // if $event->element->id is null then we haven't found a match so create an unsaved draft of the product + // so that the variants can get saved right + if (!$event->element->id) { + $originalScenario = $event->element->getScenario(); + $event->element->setScenario(\craft\base\Element::SCENARIO_ESSENTIALS); + if (!Craft::$app->getDrafts()->saveElementAsDraft($event->element, null, null, null, false)) { + throw new Exception('Unable to create product element as unsaved'); + } + $event->element->setScenario($originalScenario); + } $this->_preParseVariants($event); } }); @@ -156,13 +167,6 @@ public function setModel($settings): ElementInterface $this->element->siteId = $siteId; } - $originalScenario = $this->element->getScenario(); - $this->element->setScenario(\craft\base\Element::SCENARIO_ESSENTIALS); - if (!Craft::$app->getDrafts()->saveElementAsDraft($this->element, null, null, null, false)) { - throw new Exception('Unable to create product element as unsaved'); - } - $this->element->setScenario($originalScenario); - return $this->element; } @@ -446,9 +450,6 @@ private function _parseVariants($event): void $variants[$sku] = new VariantElement(); } - $variants[$sku]->setOwner($element); - $variants[$sku]->setPrimaryOwner($element); - // We are going to handle stock after the product and variants save $stock = null; if (isset($attributeData['stock'])) { From be219b00efc3c67e81159400abed7164a2bdda56 Mon Sep 17 00:00:00 2001 From: Iwona Just Date: Tue, 3 Sep 2024 11:52:49 +0100 Subject: [PATCH 7/8] cleanup --- src/elements/CommerceProduct.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/elements/CommerceProduct.php b/src/elements/CommerceProduct.php index 223ebac4..9c392a13 100644 --- a/src/elements/CommerceProduct.php +++ b/src/elements/CommerceProduct.php @@ -89,6 +89,12 @@ public function init(): void parent::init(); // Hook into the process service on each step - we need to re-arrange the feed mapping + Event::on(Process::class, Process::EVENT_STEP_BEFORE_ELEMENT_MATCH, function(FeedProcessEvent $event) { + if ($event->feed['elementType'] === ProductElement::class) { + $this->_checkForVariantMatches($event); + } + }); + Event::on(Process::class, Process::EVENT_STEP_BEFORE_PARSE_CONTENT, function(FeedProcessEvent $event) { if ($event->feed['elementType'] === ProductElement::class) { // at this point we've matched existing elements; @@ -106,12 +112,6 @@ public function init(): void } }); - Event::on(Process::class, Process::EVENT_STEP_BEFORE_ELEMENT_MATCH, function(FeedProcessEvent $event) { - if ($event->feed['elementType'] === ProductElement::class) { - $this->_checkForVariantMatches($event); - } - }); - // Hook into the before element save event, because we need to do lots to prepare variant data Event::on(Process::class, Process::EVENT_STEP_BEFORE_ELEMENT_SAVE, function(FeedProcessEvent $event) { if ($event->feed['elementType'] === ProductElement::class) { From af1081940d74477daf3244e5021a05b6e6c7626e Mon Sep 17 00:00:00 2001 From: Iwona Just Date: Tue, 3 Sep 2024 11:54:00 +0100 Subject: [PATCH 8/8] ecs --- src/elements/CommerceProduct.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elements/CommerceProduct.php b/src/elements/CommerceProduct.php index 9c392a13..c3451282 100644 --- a/src/elements/CommerceProduct.php +++ b/src/elements/CommerceProduct.php @@ -177,7 +177,7 @@ public function save($element, $settings): bool { $this->beforeSave($element, $settings); - if($this->element->getIsDraft()) { + if ($this->element->getIsDraft()) { $this->element->setDirtyAttributes(['variants']); $this->element = Craft::$app->getDrafts()->applyDraft($this->element); }