Skip to content

Commit

Permalink
Merge pull request #1448 from craftcms/feature/commerce-5-support
Browse files Browse the repository at this point in the history
Compatible with Commerce 5
  • Loading branch information
angrybrad authored May 27, 2024
2 parents a88ee31 + 8c86511 commit 7ed4339
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 79 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Added Craft Commerce 5 compatibility. ([#1439](https://github.com/craftcms/feed-me/issues/1439))
- You can now match elements in a feed via their Asset IDs, instead of just the filename. ([#1327](https://github.com/craftcms/feed-me/pull/1327))
- Fixed a PHP error that could occur when importing multiple values into a relational field in some scenarios. ([#1436](https://github.com/craftcms/feed-me/pull/1436))
- Fixed a SQL error that could occur when matching elements on a custom field. ([#1437](https://github.com/craftcms/feed-me/pull/1437))
Expand Down
58 changes: 57 additions & 1 deletion src/elements/CommerceProduct.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
use Carbon\Carbon;
use Craft;
use craft\base\ElementInterface;
use craft\commerce\collections\UpdateInventoryLevelCollection;
use craft\commerce\elements\Product as ProductElement;
use craft\commerce\elements\Variant as VariantElement;
use craft\commerce\models\inventory\UpdateInventoryLevel;
use craft\commerce\models\InventoryLevel;
use craft\commerce\Plugin as Commerce;
use craft\db\Query;
use craft\feedme\base\Element;
Expand Down Expand Up @@ -102,6 +105,13 @@ public function init(): void
$this->_parseVariants($event);
}
});

// We can only update stock after the purchasable elements have been saved
Event::on(Process::class, Process::EVENT_STEP_AFTER_ELEMENT_SAVE, function(FeedProcessEvent $event) {
if ($event->feed['elementType'] === ProductElement::class) {
$this->_inventoryUpdate($event);
}
});
}

/**
Expand Down Expand Up @@ -423,15 +433,27 @@ private function _parseVariants($event): void

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

// We are going to handle stock after the product and variants save
$stock = null;
if (isset($attributeData['stock'])) {
$stock = $attributeData['stock'];
unset($attributeData['stock']);
}

// Set the attributes for the element
$variants[$sku]->setAttributes($attributeData, false);

// Restore it to attribute data
if ($stock !== null) {
$attributeData['stock'] = $stock;
}

// Then, do the same for custom fields. Again, this should be done after populating the element attributes
foreach ($variantContent as $fieldHandle => $fieldInfo) {
if (Hash::get($fieldInfo, 'field')) {
$data = Hash::get($fieldInfo, 'data');

$fieldValue = Plugin::$plugin->fields->parseField($feed, $element, $data, $fieldHandle, $fieldInfo);
$fieldValue = Plugin::$plugin->fields->parseField($feed, $variants[$sku], $data, $fieldHandle, $fieldInfo);

if ($fieldValue !== null) {
$fieldData[$fieldHandle] = $fieldValue;
Expand All @@ -456,6 +478,40 @@ private function _parseVariants($event): void
$event->element = $element;
}

private function _inventoryUpdate($event): void
{

/** @var Commerce $commercePlugin */
$commercePlugin = Commerce::getInstance();
$variants = $event->element->getVariants();

$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' => '',
])
);
}
}
}

if ($updateInventoryLevels->count() > 0) {
Commerce::getInstance()->getInventory()->executeUpdateInventoryLevels($updateInventoryLevels);
}
}

/**
* @param $sku
* @param null $siteId
Expand Down
164 changes: 86 additions & 78 deletions src/templates/_includes/elements/commerce-products/map.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,40 +59,6 @@
{ label: 'Disabled', value: '0' },
],
},
}, {
name: 'Tax Category',
handle: 'taxCategoryId',
required: true,
default: {
type: 'select',
options: taxCategories,
},
}, {
name: 'Shipping Category',
handle: 'shippingCategoryId',
required: true,
default: {
type: 'select',
options: shippingCategories,
},
}, {
name: 'Available for Purchase',
handle: 'availableForPurchase',
default: {
type: 'checkbox',
},
}, {
name: 'Free Shipping',
handle: 'freeShipping',
default: {
type: 'checkbox',
},
}, {
name: 'Promotable',
handle: 'promotable',
default: {
type: 'checkbox',
},
}, {
name: 'Product ID',
handle: 'id',
Expand Down Expand Up @@ -130,7 +96,7 @@
},
}, {
name: 'Price',
handle: 'variant-price',
handle: 'variant-basePrice',
required: true,
default: {
type: 'text',
Expand All @@ -143,8 +109,8 @@
type: 'text',
},
}, {
name: 'Unlimited Stock',
handle: 'variant-hasUnlimitedStock',
name: 'Inventory Tracked',
handle: 'variant-inventoryTracked',
default: {
type: 'checkbox',
},
Expand All @@ -154,6 +120,40 @@
default: {
type: 'checkbox',
},
}, {
name: 'Promotable',
handle: 'variant-promotable',
default: {
type: 'checkbox',
},
}, {
name: 'Tax Category',
handle: 'variant-taxCategoryId',
required: true,
default: {
type: 'select',
options: taxCategories,
},
}, {
name: 'Shipping Category',
handle: 'variant-shippingCategoryId',
required: true,
default: {
type: 'select',
options: shippingCategories,
},
}, {
name: 'Available for Purchase',
handle: 'variant-availableForPurchase',
default: {
type: 'checkbox',
},
}, {
name: 'Free Shipping',
handle: 'variant-freeShipping',
default: {
type: 'checkbox',
},
}, {
name: 'Minimum allowed quantity',
handle: 'variant-minQty',
Expand Down Expand Up @@ -238,26 +238,30 @@ <h2>{{ 'Product Variant Fields'|t('feed-me') }}</h2>
{% for tab in productTabs %}
<hr>

<h2>{{ tab.name }} Fields</h2>

<table class="feedme-mapping data fullwidth collapsible">
<thead>
<th>{{ 'Field'|t('feed-me') }}</th>
<th>{{ 'Feed Element'|t('feed-me') }}</th>
<th>{{ 'Default Value'|t('feed-me') }}</th>
</thead>
<tbody>
{% for layoutField in tab.getElements()|filter(e => e is instance of('craft\\fieldlayoutelements\\CustomField')) %}
{% set field = layoutField.getField() %}
{% set fieldClass = craft.feedme.fields.getRegisteredField(className(field)) %}
{% set template = fieldClass.getMappingTemplate() %}

{% set variables = { name: field.name, handle: field.handle, feed: feed, feedData: feedData, field: field, fieldClass: fieldClass } %}

{% include template ignore missing with variables only %}
{% endfor %}
</tbody>
</table>
<h2>{{ "Product Fields"|t('feed-me') }}: {{ tab.name }}</h2>

{% if tab.getElements()|filter(e => e is instance of('craft\\fieldlayoutelements\\CustomField')) is empty %}
<p>{{ "No custom fields to map."|t('feed-me') }}</p>
{% else %}
<table class="feedme-mapping data fullwidth collapsible">
<thead>
<th>{{ 'Field'|t('feed-me') }}</th>
<th>{{ 'Feed Element'|t('feed-me') }}</th>
<th>{{ 'Default Value'|t('feed-me') }}</th>
</thead>
<tbody>
{% for layoutField in tab.getElements()|filter(e => e is instance of('craft\\fieldlayoutelements\\CustomField')) %}
{% set field = layoutField.getField() %}
{% set fieldClass = craft.feedme.fields.getRegisteredField(className(field)) %}
{% set template = fieldClass.getMappingTemplate() %}

{% set variables = { name: field.name, handle: field.handle, feed: feed, feedData: feedData, field: field, fieldClass: fieldClass } %}

{% include template ignore missing with variables only %}
{% endfor %}
</tbody>
</table>
{% endif %}
{% endfor %}
{% endif %}

Expand All @@ -269,27 +273,31 @@ <h2>{{ tab.name }} Fields</h2>
{% for tab in variantTabs %}
<hr>

<h2>{{ "Variant Fields"|t('feed-me') }}</h2>

<table class="feedme-mapping data fullwidth collapsible">
<thead>
<th>{{ 'Field'|t('feed-me') }}</th>
<th>{{ 'Feed Element'|t('feed-me') }}</th>
<th>{{ 'Default Value'|t('feed-me') }}</th>
</thead>
<tbody>
{% for layoutField in tab.getElements()|filter(e => e is instance of('craft\\fieldlayoutelements\\CustomField')) %}
{% set field = layoutField.getField() %}
{% set fieldClass = craft.feedme.fields.getRegisteredField(className(field)) %}
{% set template = fieldClass.getMappingTemplate() %}
{% set handle = 'variant-' ~ field.handle %}

{% set variables = { name: field.name, handle: handle, feed: feed, feedData: feedData, field: field, fieldClass: fieldClass } %}

{% include template ignore missing with variables only %}
{% endfor %}
</tbody>
</table>
<h2>{{ "Variant Fields"|t('feed-me') }}: {{ tab.name }}</h2>

{% if tab.getElements()|filter(e => e is instance of('craft\\fieldlayoutelements\\CustomField')) is empty %}
<p>{{ "No custom fields to map."|t('feed-me') }}</p>
{% else %}
<table class="feedme-mapping data fullwidth collapsible">
<thead>
<th>{{ 'Field'|t('feed-me') }}</th>
<th>{{ 'Feed Element'|t('feed-me') }}</th>
<th>{{ 'Default Value'|t('feed-me') }}</th>
</thead>
<tbody>
{% for layoutField in tab.getElements()|filter(e => e is instance of('craft\\fieldlayoutelements\\CustomField')) %}
{% set field = layoutField.getField() %}
{% set fieldClass = craft.feedme.fields.getRegisteredField(className(field)) %}
{% set template = fieldClass.getMappingTemplate() %}
{% set handle = 'variant-' ~ field.handle %}

{% set variables = { name: field.name, handle: handle, feed: feed, feedData: feedData, field: field, fieldClass: fieldClass } %}

{% include template ignore missing with variables only %}
{% endfor %}
</tbody>
</table>
{% endif %}
{% endfor %}
{% endif %}

Expand Down

0 comments on commit 7ed4339

Please sign in to comment.