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

Entryfication compatibility for discounts and sales #3240

Merged
merged 7 commits into from
Aug 2, 2023
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 @@

## 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
Loading