Skip to content

Commit

Permalink
Merge branch '4.7' into 4.x
Browse files Browse the repository at this point in the history
  • Loading branch information
lukeholder committed Oct 16, 2024
2 parents cd0025f + f863c39 commit cebe562
Show file tree
Hide file tree
Showing 43 changed files with 579 additions and 126 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- '4.x'
- '4.7'
pull_request:
permissions:
contents: read
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Release Notes for Craft Commerce 4.7 (WIP)

### Store Management
- It’s now possible for discounts to explicitly require a coupon code. ([#3132](https://github.com/craftcms/commerce/issues/3132))
- New order addresses now default to the store’s country on Order Edit screens. ([#3306](https://github.com/craftcms/commerce/issues/3306))
- Product conditions can now have a “Variant Search” rule. ([#3689](https://github.com/craftcms/commerce/issues/3689))

### Extensibility
- Added `craft\commerce\console\controllers\UpgradeController::$v3droppableColumns`
- Added `craft\commerce\console\controllers\UpgradeController::EVENT_BEFORE_DROP_V3_DATABASE_ENTITIES`.
- Added `craft\commerce\elements\conditions\products\ProductVariantSearchConditionRule`.
- Added `craft\commerce\events\UpgradeEvent`.
- Added `craft\commerce\models\Discount::$requireCouponCode`.

### System
- Improved the performance of adding items to the cart.
- Improved the performance of shipping rule matching when an order condition formula is used. ([3653](https://github.com/craftcms/commerce/pull/3653))
- Craft Commerce now requires Money PHP 4.2 or later.
- Fixed a bug where outstanding order balances could be calculated incorrectly. ([#3403](https://github.com/craftcms/commerce/issues/3403))
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"craftcms/cms": "^4.9.4",
"dompdf/dompdf": "^2.0.2",
"ibericode/vat": "^1.2.2",
"iio/libmergepdf": "^4.0"
"iio/libmergepdf": "^4.0",
"moneyphp/money": "^4.2.0"
},
"require-dev": {
"codeception/codeception": "^4.0.0",
Expand Down
33 changes: 32 additions & 1 deletion src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use craft\commerce\behaviors\CustomerAddressBehavior;
use craft\commerce\behaviors\CustomerBehavior;
use craft\commerce\behaviors\ValidateOrganizationTaxIdBehavior;
use craft\commerce\console\controllers\UpgradeController as UpgradeController;
use craft\commerce\db\Table;
use craft\commerce\debug\CommercePanel;
use craft\commerce\elements\Donation;
Expand Down Expand Up @@ -116,8 +117,10 @@
use craft\fixfks\controllers\RestoreController;
use craft\gql\ElementQueryConditionBuilder;
use craft\helpers\Console;
use craft\helpers\Cp;
use craft\helpers\Db;
use craft\helpers\FileHelper;
use craft\helpers\Html;
use craft\helpers\UrlHelper;
use craft\models\FieldLayout;
use craft\redactor\events\RegisterLinkOptionsEvent;
Expand Down Expand Up @@ -207,7 +210,7 @@ public static function editions(): array
/**
* @inheritDoc
*/
public string $schemaVersion = '4.5.3';
public string $schemaVersion = '4.7.0.1';

/**
* @inheritdoc
Expand Down Expand Up @@ -270,12 +273,40 @@ public function init(): void
$this->_registerDebugPanels();

if ($request->getIsCpRequest()) {
$this->_checkUpgradeCommandHasFinished();
$this->_registerStoreAddressAuthHandlers();
}
});
Craft::setAlias('@commerceLib', Craft::getAlias('@craft/commerce/../lib'));
}

private function _checkUpgradeCommandHasFinished()
{
$v3Columns = UpgradeController::$v3droppableColumns;
// do any of the columns exist:
$columnsExist = false;
foreach ($v3Columns as $column) {
if (Craft::$app->getDb()->columnExists($column['table'], $column['column'])) {
$columnsExist = true;
break;
}
}

if ($columnsExist) {
Event::on(Cp::class, Cp::EVENT_REGISTER_ALERTS, static function($event) {
$event->alerts[] = [
'content' =>
Html::tag('strong', Craft::t('commerce', '{name} upgrade incomplete.', [
'name' => '<span lang="en">Craft Commerce 4</span>',
])) . ' ' .
Craft::t('commerce', 'Please ensure the <a href="{url}">upgrade command</a> has finished running.', [
'url' => 'https://craftcms.com/docs/commerce/4.x/upgrading.html#performing-the-upgrade',
]),
];
});
}
}

/**
* @inheritdoc
*/
Expand Down
19 changes: 17 additions & 2 deletions src/console/controllers/UpgradeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use craft\commerce\console\Controller;
use craft\commerce\db\Table;
use craft\commerce\elements\conditions\addresses\PostalCodeFormulaConditionRule;
use craft\commerce\events\UpgradeEvent;
use craft\commerce\Plugin;
use craft\commerce\records\Store;
use craft\db\Connection;
Expand Down Expand Up @@ -51,6 +52,13 @@
*/
class UpgradeController extends Controller
{
/**
* @event UpgradeEvent The event that is triggered before the v3 columns and tables are dropped during upgrade.
* @see actionRun()
* @since 4.7.0
*/
public const EVENT_BEFORE_DROP_V3_DATABASE_ENTITIES = 'beforeDropV3DatabaseEntities';

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -96,7 +104,7 @@ private function _getOrphanedCustomerIds(): array
*
* @var array<array{table: string, column: string}>
*/
private array $_v3droppableColumns = [
public static array $v3droppableColumns = [
['table' => '{{%commerce_taxzones}}', 'column' => 'v3isCountryBased'],
['table' => '{{%commerce_shippingzones}}', 'column' => 'v3isCountryBased'],
['table' => '{{%commerce_taxzones}}', 'column' => 'v3zipCodeConditionFormula'],
Expand Down Expand Up @@ -316,13 +324,20 @@ public function actionRun(): int
return ExitCode::UNSPECIFIED_ERROR;
}

$event = new UpgradeEvent();
$event->v3columnMap = self::$v3droppableColumns;
$event->v3tables = $this->_v3tables;
if ($this->hasEventHandlers(self::EVENT_BEFORE_DROP_V3_DATABASE_ENTITIES)) {
$this->trigger(self::EVENT_BEFORE_DROP_V3_DATABASE_ENTITIES, $event);
}

$this->stdout("Cleaning up tables…");
foreach ($this->_v3tables as $table) {
Db::dropAllForeignKeysToTable($table, $this->db);
$this->db->createCommand()->dropTableIfExists($table)->execute();
}

foreach ($this->_v3droppableColumns as ['table' => $table, 'column' => $column]) {
foreach (static::$v3droppableColumns as ['table' => $table, 'column' => $column]) {
if ($this->db->columnExists($table, $column)) {
Db::dropForeignKeyIfExists($table, $column, $this->db);
Db::dropIndexIfExists($table, $column, db: $this->db);
Expand Down
10 changes: 6 additions & 4 deletions src/controllers/CartController.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public function actionUpdateCart(): ?Response

// Get the cart from the request or from the session.
// When we are about to update the cart, we consider it a real cart at this point, and want to actually create it in the DB.
$this->_cart = $this->_getCart(true);
$this->_cart = $this->_getCart();

// Can clear line items when updating the cart
$clearLineItems = $this->request->getParam('clearLineItems');
Expand Down Expand Up @@ -580,7 +580,7 @@ private function _getCart(bool $forceSave = false): Order
// Get the cart from the order number
$cart = Order::find()->number($orderNumber)->isCompleted(false)->one();

if (!$cart) {
if ($cart === null) {
throw new NotFoundHttpException('Cart not found');
}

Expand All @@ -590,7 +590,9 @@ private function _getCart(bool $forceSave = false): Order
$requestForceSave = (bool)$this->request->getBodyParam('forceSave');
$doForceSave = ($requestForceSave || $forceSave);

return Plugin::getInstance()->getCarts()->getCart($doForceSave);
$this->_cart = Plugin::getInstance()->getCarts()->getCart($doForceSave);

return $this->_cart;
}

/**
Expand Down Expand Up @@ -685,7 +687,7 @@ private function _setAddresses(): void
$this->_cart->sourceBillingAddressId = $billingAddressId;

/** @var Address $cartBillingAddress */
$cartBillingAddress = Craft::$app->getElements()->duplicateElement($userBillingAddress, [
$cartBillingAddress = Craft::$app->getElements()->duplicateElement($userBillingAddress, [
'owner' => $this->_cart,
]);
$this->_cart->setBillingAddress($cartBillingAddress);
Expand Down
9 changes: 4 additions & 5 deletions src/controllers/DiscountsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,10 @@ public function actionTableData(): Response
'discounts.dateTo',
'discounts.totalDiscountUses',
'discounts.ignoreSales',
'discounts.requireCouponCode',
'discounts.stopProcessing',
'discounts.sortOrder',
'coupons.discountId',
])
->distinct()
->leftJoin(Table::COUPONS . ' coupons', '[[coupons.discountId]] = [[discounts.id]]')
->orderBy(['sortOrder' => SORT_ASC]);


Expand Down Expand Up @@ -146,8 +144,7 @@ public function actionTableData(): Response
'status' => (bool)$item['enabled'],
'duration' => $dateRange,
'timesUsed' => $item['totalDiscountUses'],
// If there is joined data then there are coupons
'hasCoupons' => (bool)$item['discountId'],
'requireCouponCode' => (bool)$item['requireCouponCode'],
'ignore' => (bool)$item['ignoreSales'],
'stop' => (bool)$item['stopProcessing'],
];
Expand Down Expand Up @@ -223,6 +220,7 @@ public function actionSave(): ?Response
$discount->setCustomerCondition($this->request->getBodyParam('customerCondition'));
$discount->setShippingAddressCondition($this->request->getBodyParam('shippingAddressCondition'));
$discount->setBillingAddressCondition($this->request->getBodyParam('billingAddressCondition'));
$discount->requireCouponCode = (bool)$this->request->getBodyParam('requireCouponCode');
$discount->stopProcessing = (bool)$this->request->getBodyParam('stopProcessing');
$discount->purchaseQty = $this->request->getBodyParam('purchaseQty');
$discount->maxPurchaseQty = $this->request->getBodyParam('maxPurchaseQty');
Expand Down Expand Up @@ -340,6 +338,7 @@ public function actionSave(): ?Response
private function _setCouponsOnDiscount(array $coupons, Discount $discount): void
{
if (empty($coupons)) {
$discount->setCoupons([]);
return;
}

Expand Down
3 changes: 3 additions & 0 deletions src/controllers/OrdersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,9 @@ private function _registerJavascript(array $variables): void
$forceEdit = ($variables['order']->hasErrors() || !$variables['order']->isCompleted);

Craft::$app->getView()->registerJs('window.orderEdit.forceEdit = ' . Json::encode($forceEdit) . ';', View::POS_BEGIN);

$store = Plugin::getInstance()->getStore()->getStore();
Craft::$app->getView()->registerJs('window.orderEdit.store = ' . Json::encode($store->toArray([], ['locationAddress'])) . ';', View::POS_BEGIN);
}

/**
Expand Down
34 changes: 23 additions & 11 deletions src/elements/Order.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
use craft\i18n\Locale;
use craft\models\Site;
use DateTime;
use Money\Teller;
use ReflectionClass;
use ReflectionMethod;
use ReflectionNamedType;
Expand Down Expand Up @@ -2441,15 +2442,18 @@ public function isPaymentAmountPartial(): bool
*/
public function getPaidStatus(): string
{
if ($this->getIsPaid() && $this->getTotalPrice() > 0 && $this->getTotalPaid() > $this->getTotalPrice()) {
if ($this->getIsPaid() &&
$this->_getTeller()->greaterThan($this->getTotalPrice(), 0) &&
$this->_getTeller()->greaterThan($this->getTotalPaid(), $this->getTotalPrice())
) {
return self::PAID_STATUS_OVERPAID;
}

if ($this->getIsPaid()) {
return self::PAID_STATUS_PAID;
}

if ($this->getTotalPaid() > 0) {
if ($this->_getTeller()->greaterThan($this->getTotalPaid(), 0)) {
return self::PAID_STATUS_PARTIAL;
}

Expand Down Expand Up @@ -2553,20 +2557,18 @@ public function hasShippableItems(): bool

/**
* Returns the difference between the order amount and amount paid.
*
*
*/
public function getOutstandingBalance(): float
{
$totalPaid = Currency::round($this->getTotalPaid());
$totalPrice = $this->getTotalPrice(); // Already rounded

return $totalPrice - $totalPaid;
return (float)$this->_getTeller()->subtract($this->getTotalPrice(), $this->getTotalPaid());
}

/**
* @return bool
*/
public function hasOutstandingBalance(): bool
{
return $this->getOutstandingBalance() > 0;
return $this->_getTeller()->greaterThan($this->getOutstandingBalance(), 0);
}

/**
Expand All @@ -2593,7 +2595,7 @@ public function getTotalPaid(): float
$paid = array_sum(ArrayHelper::getColumn($paidTransactions, 'amount', false));
$refunded = array_sum(ArrayHelper::getColumn($refundedTransactions, 'amount', false));

return $paid - $refunded;
return (float)$this->_getTeller()->subtract($paid, $refunded);
}

/**
Expand Down Expand Up @@ -2631,7 +2633,7 @@ public function getTotalAuthorized(): float
}
}

return $authorized - $captured;
return (float)$this->_getTeller()->subtract($authorized, $captured);
}

/**
Expand Down Expand Up @@ -3611,4 +3613,14 @@ private function _populateAddressNameAttributes(AddressElement $addressElement,
}
}
}

/**
* @return Teller
* @throws InvalidConfigException
* @since 4.7.0
*/
private function _getTeller(): Teller
{
return Plugin::getInstance()->getCurrencies()->getTeller($this->currency);
}
}
1 change: 1 addition & 0 deletions src/elements/conditions/products/ProductCondition.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ protected function conditionRuleTypes(): array
{
return array_merge(parent::conditionRuleTypes(), [
ProductTypeConditionRule::class,
ProductVariantSearchConditionRule::class,
ProductVariantSkuConditionRule::class,
ProductVariantStockConditionRule::class,
ProductVariantHasUnlimitedStockConditionRule::class,
Expand Down
Loading

0 comments on commit cebe562

Please sign in to comment.