Skip to content

Commit

Permalink
Merge pull request #1490 from craftcms/bugfix/stock
Browse files Browse the repository at this point in the history
Fix stock import for Product Variants
  • Loading branch information
angrybrad authored Sep 26, 2024
2 parents abadde3 + af10819 commit 1880bf6
Showing 1 changed file with 75 additions and 25 deletions.
100 changes: 75 additions & 25 deletions src/elements/CommerceProduct.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,6 +22,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;
Expand Down Expand Up @@ -87,15 +89,26 @@ 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_PARSE_CONTENT, function(FeedProcessEvent $event) {
Event::on(Process::class, Process::EVENT_STEP_BEFORE_ELEMENT_MATCH, function(FeedProcessEvent $event) {
if ($event->feed['elementType'] === ProductElement::class) {
$this->_preParseVariants($event);
$this->_checkForVariantMatches($event);
}
});

Event::on(Process::class, Process::EVENT_STEP_BEFORE_ELEMENT_MATCH, function(FeedProcessEvent $event) {
Event::on(Process::class, Process::EVENT_STEP_BEFORE_PARSE_CONTENT, function(FeedProcessEvent $event) {
if ($event->feed['elementType'] === ProductElement::class) {
$this->_checkForVariantMatches($event);
// 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);
}
});

Expand Down Expand Up @@ -164,6 +177,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()];

Expand Down Expand Up @@ -274,6 +292,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');
Expand Down Expand Up @@ -431,8 +450,6 @@ private function _parseVariants($event): void
$variants[$sku] = new VariantElement();
}

$variants[$sku]->product = $element;

// We are going to handle stock after the product and variants save
$stock = null;
if (isset($attributeData['stock'])) {
Expand Down Expand Up @@ -480,31 +497,64 @@ 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' => '',
])
);
}
// 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 import 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);

/** @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) {
Expand Down

0 comments on commit 1880bf6

Please sign in to comment.