Skip to content

Commit

Permalink
Merge pull request #3240 from craftcms/feature/entryfication-compatib…
Browse files Browse the repository at this point in the history
…ility

Entryfication compatibility for discounts and sales
  • Loading branch information
lukeholder authored Aug 2, 2023
2 parents 259d3f9 + 3900a20 commit 993b3c1
Show file tree
Hide file tree
Showing 11 changed files with 301 additions and 151 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 @@

## 4.3.0 - Unreleased

- Sales and Discounts now support using related entries in their conditions. ([#3134](https://github.com/craftcms/commerce/issues/3134))
- It’s now possible to query products by shipping category and tax category. ([#3219](https://github.com/craftcms/commerce/issues/3219))
- It’s now possible to modify the purchasables shown in the add line item table on the Edit Order page. ([#3194](https://github.com/craftcms/commerce/issues/3194))
- Added `craft\commerce\events\ModifyPurchasablesQueryEvent`.
Expand Down
47 changes: 34 additions & 13 deletions src/controllers/DiscountsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use craft\commerce\services\Coupons;
use craft\commerce\web\assets\coupons\CouponsAsset;
use craft\elements\Category;
use craft\elements\Entry;
use craft\errors\MissingComponentException;
use craft\helpers\ArrayHelper;
use craft\helpers\DateTimeHelper;
Expand Down Expand Up @@ -113,7 +114,7 @@ public function actionEdit(int $id = null, Discount $discount = null): Response
/**
* @throws HttpException
*/
public function actionSave(): void
public function actionSave(): ?Response
{
$this->requirePostRequest();

Expand Down Expand Up @@ -183,7 +184,8 @@ public function actionSave(): void
$discount->percentDiscount = -Localization::normalizePercentage($percentDiscount);

// Set purchasable conditions
if ($discount->allPurchasables = (bool)$this->request->getBodyParam('allPurchasables')) {
$allPurchasables = !$this->request->getBodyParam('allPurchasables', false);
if ($discount->allPurchasables = $allPurchasables) {
$discount->setPurchasableIds([]);
} else {
$purchasables = [];
Expand All @@ -197,15 +199,21 @@ public function actionSave(): void
$discount->setPurchasableIds($purchasables);
}

// False in the allCategories param is true in the DB
$allCategories = !$this->request->getBodyParam('allCategories', false);
// Set category conditions
if ($discount->allCategories = (bool)$this->request->getBodyParam('allCategories')) {
if ($discount->allCategories = $allCategories) {
$discount->setCategoryIds([]);
} else {
$categories = $this->request->getBodyParam('categories', []);
if (!$categories) {
$categories = [];
$relatedElements = [];
$relatedElementByType = $this->request->getBodyParam('relatedElements') ?: [];
foreach ($relatedElementByType as $type) {
if (is_array($type)) {
array_push($relatedElements, ...$type);
}
}
$discount->setCategoryIds($categories);
$relatedElements = array_unique($relatedElements);
$discount->setCategoryIds($relatedElements);
}

$coupons = $this->request->getBodyParam('coupons') ?: [];
Expand All @@ -214,7 +222,7 @@ public function actionSave(): void
// Save it
if (Plugin::getInstance()->getDiscounts()->saveDiscount($discount)) {
$this->setSuccessFlash(Craft::t('commerce', 'Discount saved.'));
$this->redirectToPostedUrl($discount);
return $this->redirectToPostedUrl($discount);
} else {
$this->setFailFlash(Craft::t('commerce', 'Couldn’t save discount.'));

Expand All @@ -230,6 +238,8 @@ public function actionSave(): void
$this->_populateVariables($variables);

Craft::$app->getUrlManager()->setRouteParams($variables);

return null;
}

/**
Expand Down Expand Up @@ -489,8 +499,12 @@ private function _populateVariables(array &$variables): void
}

$variables['categoryElementType'] = Category::class;
$variables['entryElementType'] = Entry::class;
$variables['categories'] = null;
$variables['entries'] = null;

$categories = [];
$entries = [];

if (empty($variables['id']) && $this->request->getParam('categoryIds')) {
$categoryIds = explode('|', $this->request->getParam('categoryIds'));
Expand All @@ -500,15 +514,22 @@ private function _populateVariables(array &$variables): void

foreach ($categoryIds as $categoryId) {
$id = (int)$categoryId;
$categories[] = Craft::$app->getElements()->getElementById($id);
$element = Craft::$app->getElements()->getElementById($id);

if ($element instanceof Category) {
$categories[] = $element;
} elseif ($element instanceof Entry) {
$entries[] = $element;
}
}

$variables['categories'] = $categories;
$variables['entries'] = $entries;

$variables['categoryRelationshipTypeOptions'] = [
DiscountRecord::CATEGORY_RELATIONSHIP_TYPE_SOURCE => Craft::t('commerce', 'Source - The category relationship field is on the purchasable'),
DiscountRecord::CATEGORY_RELATIONSHIP_TYPE_TARGET => Craft::t('commerce', 'Target - The purchasable relationship field is on the category'),
DiscountRecord::CATEGORY_RELATIONSHIP_TYPE_BOTH => Craft::t('commerce', 'Either (Default) - The relationship field is on the purchasable or the category'),
$variables['elementRelationshipTypeOptions'] = [
DiscountRecord::CATEGORY_RELATIONSHIP_TYPE_SOURCE => Craft::t('commerce', 'The purchasable defines the relationship'),
DiscountRecord::CATEGORY_RELATIONSHIP_TYPE_TARGET => Craft::t('commerce', 'The purchasable is related by another element'),
DiscountRecord::CATEGORY_RELATIONSHIP_TYPE_BOTH => Craft::t('commerce', 'Either way'),
];

$variables['appliedTo'] = [
Expand Down
44 changes: 33 additions & 11 deletions src/controllers/SalesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use craft\commerce\Plugin;
use craft\commerce\records\Sale as SaleRecord;
use craft\elements\Category;
use craft\elements\Entry;
use craft\helpers\ArrayHelper;
use craft\helpers\DateTimeHelper;
use craft\helpers\Json;
Expand Down Expand Up @@ -76,6 +77,8 @@ public function actionEdit(int $id = null, Sale $sale = null): Response

$variables = compact('id', 'sale');

$variables['isNewSale'] = false;

if (!$variables['sale']) {
if ($variables['id']) {
$variables['sale'] = Plugin::getInstance()->getSales()->getSaleById($variables['id']);
Expand All @@ -85,6 +88,7 @@ public function actionEdit(int $id = null, Sale $sale = null): Response
}
} else {
$variables['sale'] = new Sale();
$variables['isNewSale'] = true;
$variables['sale']->allCategories = true;
$variables['sale']->allPurchasables = true;
$variables['sale']->allGroups = true;
Expand Down Expand Up @@ -152,7 +156,8 @@ public function actionSave(): ?Response
}

// Set purchasable conditions
if ($sale->allPurchasables = (bool)$this->request->getBodyParam('allPurchasables')) {
$allPurchasables = !$this->request->getBodyParam('allPurchasables', false);
if ($sale->allPurchasables = $allPurchasables) {
$sale->setPurchasableIds([]);
} else {
$purchasables = [];
Expand All @@ -165,15 +170,21 @@ public function actionSave(): ?Response
$sale->setPurchasableIds($purchasables);
}

// False in the allCategories param is true in the DB
$allCategories = !$this->request->getBodyParam('allCategories', false);
// Set category conditions
if ($sale->allCategories = (bool)$this->request->getBodyParam('allCategories')) {
if ($sale->allCategories = $allCategories) {
$sale->setCategoryIds([]);
} else {
$categories = $this->request->getBodyParam('categories', []);
if (!$categories) {
$categories = [];
$relatedElements = [];
$relatedElementByType = $this->request->getBodyParam('relatedElements') ?: [];
foreach ($relatedElementByType as $type) {
if (is_array($type)) {
array_push($relatedElements, ...$type);
}
}
$sale->setCategoryIds($categories);
$relatedElements = array_unique($relatedElements);
$sale->setCategoryIds($relatedElements);
}

// Set user group conditions
Expand Down Expand Up @@ -458,8 +469,12 @@ private function _populateVariables(&$variables): void
}

$variables['categoryElementType'] = Category::class;
$variables['entryElementType'] = Entry::class;
$variables['categories'] = null;
$variables['entries'] = null;

$categories = [];
$entries = [];

if (empty($variables['id']) && $this->request->getParam('categoryIds')) {
$categoryIds = explode('|', $this->request->getParam('categoryIds'));
Expand All @@ -469,15 +484,22 @@ private function _populateVariables(&$variables): void

foreach ($categoryIds as $categoryId) {
$id = (int)$categoryId;
$categories[] = Craft::$app->getElements()->getElementById($id);
$element = Craft::$app->getElements()->getElementById($id);

if ($element instanceof Category) {
$categories[] = $element;
} elseif ($element instanceof Entry) {
$entries[] = $element;
}
}

$variables['categories'] = $categories;
$variables['entries'] = $entries;

$variables['categoryRelationshipType'] = [
SaleRecord::CATEGORY_RELATIONSHIP_TYPE_SOURCE => Craft::t('commerce', 'Source - The category relationship field is on the purchasable'),
SaleRecord::CATEGORY_RELATIONSHIP_TYPE_TARGET => Craft::t('commerce', 'Target - The purchasable relationship field is on the category'),
SaleRecord::CATEGORY_RELATIONSHIP_TYPE_BOTH => Craft::t('commerce', 'Either (Default) - The relationship field is on the purchasable or the category'),
$variables['elementRelationshipTypeOptions'] = [
SaleRecord::CATEGORY_RELATIONSHIP_TYPE_SOURCE => Craft::t('commerce', 'The purchasable defines the relationship'),
SaleRecord::CATEGORY_RELATIONSHIP_TYPE_TARGET => Craft::t('commerce', 'The purchasable is related by another element'),
SaleRecord::CATEGORY_RELATIONSHIP_TYPE_BOTH => Craft::t('commerce', 'Either way'),
];

$variables['purchasables'] = null;
Expand Down
6 changes: 4 additions & 2 deletions src/migrations/Install.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ public function createTables(): void
'uid' => $this->uid(),
]);

// TODO: rename to `discount_entries` table in Commerce 5 or remove if purchasable condition builder can replace it
$this->archiveTableIfExists(Table::DISCOUNT_CATEGORIES);
$this->createTable(Table::DISCOUNT_CATEGORIES, [
'id' => $this->primaryKey(),
Expand Down Expand Up @@ -558,6 +559,7 @@ public function createTables(): void
'uid' => $this->uid(),
]);

// TODO: rename to `sale_entries` table in Commerce 5 or remove if purchasable condition builder can replace it
$this->archiveTableIfExists(Table::SALE_CATEGORIES);
$this->createTable(Table::SALE_CATEGORIES, [
'id' => $this->primaryKey(),
Expand Down Expand Up @@ -935,7 +937,7 @@ public function addForeignKeys(): void
$this->addForeignKey(null, Table::CUSTOMERS, ['primaryPaymentSourceId'], Table::PAYMENTSOURCES, ['id'], 'SET NULL');
$this->addForeignKey(null, Table::CUSTOMER_DISCOUNTUSES, ['customerId'], CraftTable::ELEMENTS, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::CUSTOMER_DISCOUNTUSES, ['discountId'], Table::DISCOUNTS, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::DISCOUNT_CATEGORIES, ['categoryId'], '{{%categories}}', ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::DISCOUNT_CATEGORIES, ['categoryId'], CraftTable::ELEMENTS, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::DISCOUNT_CATEGORIES, ['discountId'], Table::DISCOUNTS, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::DISCOUNT_PURCHASABLES, ['discountId'], Table::DISCOUNTS, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::DISCOUNT_PURCHASABLES, ['purchasableId'], Table::PURCHASABLES, ['id'], 'CASCADE', 'CASCADE');
Expand Down Expand Up @@ -980,7 +982,7 @@ public function addForeignKeys(): void
$this->addForeignKey(null, Table::PRODUCTTYPES_TAXCATEGORIES, ['productTypeId'], Table::PRODUCTTYPES, ['id'], 'CASCADE');
$this->addForeignKey(null, Table::PRODUCTTYPES_TAXCATEGORIES, ['taxCategoryId'], Table::TAXCATEGORIES, ['id'], 'CASCADE');
$this->addForeignKey(null, Table::PURCHASABLES, ['id'], '{{%elements}}', ['id'], 'CASCADE');
$this->addForeignKey(null, Table::SALE_CATEGORIES, ['categoryId'], '{{%categories}}', ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::SALE_CATEGORIES, ['categoryId'], CraftTable::ELEMENTS, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::SALE_CATEGORIES, ['saleId'], Table::SALES, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::SALE_PURCHASABLES, ['purchasableId'], Table::PURCHASABLES, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::SALE_PURCHASABLES, ['saleId'], Table::SALES, ['id'], 'CASCADE', 'CASCADE');
Expand Down
43 changes: 43 additions & 0 deletions src/migrations/m230724_080855_entrify_promotions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace craft\commerce\migrations;

use craft\commerce\db\Table;
use craft\db\Migration;
use craft\db\Table as CraftTable;
use craft\helpers\Db;

/**
* m230724_080855_entrify_promotions migration.
*/
class m230724_080855_entrify_promotions extends Migration
{
/**
* @inheritdoc
*/
public function safeUp(): bool
{
// Drop all FKs
Db::dropForeignKeyIfExists(Table::DISCOUNT_CATEGORIES, ['categoryId'], $this->db);
Db::dropForeignKeyIfExists(Table::DISCOUNT_CATEGORIES, ['discountId'], $this->db);
Db::dropForeignKeyIfExists(Table::SALE_CATEGORIES, ['categoryId'], $this->db);
Db::dropForeignKeyIfExists(Table::SALE_CATEGORIES, ['saleId'], $this->db);

// Add the FKs back but to the Elements table not the categories table
$this->addForeignKey(null, Table::DISCOUNT_CATEGORIES, ['categoryId'], CraftTable::ELEMENTS, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::DISCOUNT_CATEGORIES, ['discountId'], Table::DISCOUNTS, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::SALE_CATEGORIES, ['categoryId'], CraftTable::ELEMENTS, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::SALE_CATEGORIES, ['saleId'], Table::SALES, ['id'], 'CASCADE', 'CASCADE');

return true;
}

/**
* @inheritdoc
*/
public function safeDown(): bool
{
echo "m230724_080855_entrify_promotions cannot be reverted.\n";
return false;
}
}
4 changes: 4 additions & 0 deletions src/models/Discount.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,15 @@ class Discount extends Model

/**
* @var bool Match all product types
*
* TODO: Rename to $allEntries in Commerce 5
*/
public bool $allCategories = false;

/**
* @var string Type of relationship between Categories and Products
*
* TODO: Rename to $entryRelationshipType in Commerce 5
*/
public string $categoryRelationshipType = DiscountRecord::CATEGORY_RELATIONSHIP_TYPE_BOTH;

Expand Down
18 changes: 13 additions & 5 deletions src/services/Discounts.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use craft\commerce\records\EmailDiscountUse as EmailDiscountUseRecord;
use craft\db\Query;
use craft\elements\Category;
use craft\elements\Entry;
use craft\elements\User;
use craft\helpers\ArrayHelper;
use craft\helpers\DateTimeHelper;
Expand Down Expand Up @@ -423,8 +424,10 @@ public function getDiscountsRelatedToPurchasable(PurchasableInterface $purchasab
$relatedTo = [$discount->categoryRelationshipType => $purchasable->getPromotionRelationSource()];
$categoryIds = $discount->getCategoryIds();
$relatedCategories = Category::find()->id($categoryIds)->relatedTo($relatedTo)->ids();
$relatedEntries = Entry::find()->id($categoryIds)->relatedTo($relatedTo)->ids();
$relatedCategoriesOrEntries = array_merge($relatedCategories, $relatedEntries);

if (in_array($id, $purchasableIds, false) || !empty($relatedCategories)) {
if (in_array($id, $purchasableIds, false) || !empty($relatedCategoriesOrEntries)) {
$discounts[$discount->id] = $discount;
}
}
Expand Down Expand Up @@ -459,13 +462,18 @@ public function matchLineItem(LineItem $lineItem, Discount $discount, bool $matc
return false;
}

// TODO: Rename to allEntries in Commerce 5
if (!$discount->allCategories) {
$key = 'relationshipType:' . $discount->categoryRelationshipType . ':purchasableId:' . $purchasable->getId() . ':categoryIds:' . implode('|', $discount->getCategoryIds());

if (!isset($this->_matchingLineItemCategoryCondition[$key])) {
$relatedTo = [$discount->categoryRelationshipType => $purchasable->getPromotionRelationSource()];

$relatedEntries = Entry::find()->relatedTo($relatedTo)->ids();
$relatedCategories = Category::find()->relatedTo($relatedTo)->ids();
$purchasableIsRelateToOneOrMoreCategories = (bool)array_intersect($relatedCategories, $discount->getCategoryIds());

$relatedCategoriesOrEntries = array_merge($relatedEntries, $relatedCategories);
$purchasableIsRelateToOneOrMoreCategories = (bool)array_intersect($relatedCategoriesOrEntries, $discount->getCategoryIds());
if (!$purchasableIsRelateToOneOrMoreCategories) {
return $this->_matchingLineItemCategoryCondition[$key] = false;
}
Expand Down Expand Up @@ -1103,7 +1111,7 @@ private function _populateDiscounts(array $discounts): array
}

$purchasables = [];
$categories = [];
$categoriesOrEntries = [];

foreach ($discounts as $discount) {
$id = $discount['id'];
Expand All @@ -1112,7 +1120,7 @@ private function _populateDiscounts(array $discounts): array
}

if ($discount['categoryId']) {
$categories[$id][] = $discount['categoryId'];
$categoriesOrEntries[$id][] = $discount['categoryId'];
}

unset($discount['purchasableId'], $discount['categoryId']);
Expand All @@ -1124,7 +1132,7 @@ private function _populateDiscounts(array $discounts): array

foreach ($allDiscountsById as $id => $discount) {
$discount->setPurchasableIds($purchasables[$id] ?? []);
$discount->setCategoryIds($categories[$id] ?? []);
$discount->setCategoryIds($categoriesOrEntries[$id] ?? []);
}

return $allDiscountsById;
Expand Down
Loading

0 comments on commit 993b3c1

Please sign in to comment.