Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatible with Commerce 5 #1448

Merged
merged 5 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading