Skip to content

Commit 4dc4a99

Browse files
committed
disabled discount when only customer or customer group is deleted
1 parent 33cca37 commit 4dc4a99

21 files changed

Lines changed: 439 additions & 20 deletions

File tree

admin-dev/themes/new-theme/js/components/form/customer-search-input.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ export default class CustomerSearchInput extends EntitySearchInput {
1616
shopIdCallback: () => number|null,
1717
disablingSwitchEvent?: string|undefined,
1818
) {
19-
super($(customerSearchContainer), {
19+
const $container = $(customerSearchContainer);
20+
const disabledBadgeLabel: string = $container.data('disabledBadgeLabel') as string;
21+
const guestBadgeLabel: string = $container.data('guestBadgeLabel') as string;
22+
23+
super($container, {
2024
extraQueryParams: () => ({
2125
shopId: shopIdCallback(),
2226
}),
@@ -27,7 +31,16 @@ export default class CustomerSearchInput extends EntitySearchInput {
2731

2832
return Object.values(response.customers);
2933
},
30-
34+
suggestionTemplate: (entity: any) => {
35+
const guestBadge = String(entity.is_guest) === '1'
36+
? `<span class="customer-suggestion-guest-badge badge badge-pill badge-secondary">${guestBadgeLabel}</span> `
37+
: '';
38+
const disabledBadge = String(entity.active) === '0'
39+
? ` <span class="customer-suggestion-disabled-badge badge badge-pill badge-secondary">${disabledBadgeLabel}</span>`
40+
: '';
41+
42+
return `<div class="search-suggestion">${guestBadge}${entity.fullname_and_email}${disabledBadge}</div>`;
43+
},
3144
});
3245
this.disablingSwitchEvent = disablingSwitchEvent;
3346
this.customerItemSelector = customerItemSelector;

admin-dev/themes/new-theme/scss/pages/_discount.scss

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,45 @@
2222
}
2323
}
2424

25+
.entity-search-widget {
26+
.customer-disabled-badge,
27+
.customer-guest-badge {
28+
display: none;
29+
color: $gray-800;
30+
}
31+
32+
.customer-disabled-badge {
33+
margin-right: 0.25rem;
34+
margin-left: auto;
35+
}
36+
37+
.customer-guest-badge {
38+
margin-right: 0.5rem;
39+
}
40+
41+
[data-customer-active="0"] .customer-disabled-badge {
42+
display: inline-block;
43+
}
44+
45+
[data-customer-guest="1"] .customer-guest-badge {
46+
display: inline-block;
47+
}
48+
49+
.customer-suggestion-disabled-badge,
50+
.customer-suggestion-guest-badge {
51+
display: none;
52+
margin-left: 0.35rem;
53+
color: $gray-800;
54+
}
55+
56+
.tt-suggestion {
57+
.customer-suggestion-disabled-badge,
58+
.customer-suggestion-guest-badge {
59+
display: inline-block;
60+
}
61+
}
62+
}
63+
2564
.specific-product-item {
2665
.form-group {
2766
margin-bottom: 0;

classes/CartRule.php

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -458,18 +458,22 @@ public static function getCustomerCartRules(
458458
* Remove cart rule that does not match the customer groups.
459459
* Even if empty $id_customer was provided, we will still get
460460
* a visitor group.
461+
* When the groups feature is inactive the restriction is ignored dynamically
462+
* (handled in checkValidity), so we keep all rules here.
461463
*/
462-
$customerGroups = Customer::getGroupsStatic($id_customer);
464+
if (Group::isFeatureActive()) {
465+
$customerGroups = Customer::getGroupsStatic($id_customer);
463466

464-
foreach ($result as $key => $cart_rule) {
465-
if ($cart_rule['group_restriction']) {
466-
$cartRuleGroups = Db::getInstance()->executeS('SELECT id_group FROM ' . _DB_PREFIX_ . 'cart_rule_group WHERE id_cart_rule = ' . (int) $cart_rule['id_cart_rule']);
467-
foreach ($cartRuleGroups as $cartRuleGroup) {
468-
if (in_array($cartRuleGroup['id_group'], $customerGroups)) {
469-
continue 2;
467+
foreach ($result as $key => $cart_rule) {
468+
if ($cart_rule['group_restriction']) {
469+
$cartRuleGroups = Db::getInstance()->executeS('SELECT id_group FROM ' . _DB_PREFIX_ . 'cart_rule_group WHERE id_cart_rule = ' . (int) $cart_rule['id_cart_rule']);
470+
foreach ($cartRuleGroups as $cartRuleGroup) {
471+
if (in_array($cartRuleGroup['id_group'], $customerGroups)) {
472+
continue 2;
473+
}
470474
}
475+
unset($result[$key]);
471476
}
472-
unset($result[$key]);
473477
}
474478
}
475479

@@ -834,6 +838,9 @@ public function checkValidity(Context $context, $alreadyInCart = false, $display
834838

835839
// Get an intersection of the customer groups and the cart rule groups (if the customer is not logged in, the default group is Visitors)
836840
if ($this->group_restriction) {
841+
if (!Group::isFeatureActive()) {
842+
return (!$display_error) ? false : $this->trans('You cannot use this voucher', [], 'Shop.Notifications.Error');
843+
}
837844
$id_cart_rule = (int) Db::getInstance()->getValue('
838845
SELECT crg.id_cart_rule
839846
FROM ' . _DB_PREFIX_ . 'cart_rule_group crg
@@ -2012,7 +2019,7 @@ public static function autoAddToCart(?Context $context = null, bool $useOrderPri
20122019
)
20132020
AND (
20142021
cr.`group_restriction` = 0
2015-
' . (Validate::isLoadedObject($context->customer) ? 'OR EXISTS (
2022+
' . (Group::isFeatureActive() && Validate::isLoadedObject($context->customer) ? 'OR EXISTS (
20162023
SELECT 1
20172024
FROM `' . _DB_PREFIX_ . 'customer_group` cg
20182025
INNER JOIN `' . _DB_PREFIX_ . 'cart_rule_group` crg ON cg.id_group = crg.id_group
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
/**
3+
* For the full copyright and license information, please view the
4+
* docs/licenses/LICENSE.txt file that was distributed with this source code.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace PrestaShop\PrestaShop\Adapter\CartRule;
10+
11+
use CartRule;
12+
use Db;
13+
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings;
14+
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagStateCheckerInterface;
15+
use PrestaShopCollection;
16+
17+
/**
18+
* Handles disabling of cart rules when their eligibility conditions are removed
19+
* (e.g. a customer or customer group is deleted).
20+
*/
21+
class CartRuleDisablerService
22+
{
23+
public function __construct(
24+
private readonly FeatureFlagStateCheckerInterface $featureFlagStateChecker,
25+
) {
26+
}
27+
28+
/**
29+
* On customer deletion: disable cart rules that were restricted to this customer.
30+
* Resets the customer restriction so the discount is no longer tied to the deleted customer,
31+
* and disables it so the merchant can review and re-enable it manually.
32+
*
33+
* @param int $customerId
34+
*/
35+
public function disableCartRulesThatHadCustomer(int $customerId): bool
36+
{
37+
if (!$this->featureFlagStateChecker->isEnabled(FeatureFlagSettings::FEATURE_FLAG_DISCOUNT)) {
38+
return true;
39+
}
40+
41+
if (empty($customerId)) {
42+
return false;
43+
}
44+
45+
$cartRules = new PrestaShopCollection('CartRule');
46+
$cartRules->where('id_customer', '=', $customerId);
47+
48+
$result = true;
49+
foreach ($cartRules as $cartRule) {
50+
$cartRule->id_customer = 0;
51+
$cartRule->active = false;
52+
$result = $cartRule->update() && $result;
53+
}
54+
55+
return $result;
56+
}
57+
58+
/**
59+
* On group deletion: disable cart rules that had only this group as their restriction.
60+
* Clears the group restriction and disables the discount so the merchant can review it.
61+
* Must be called BEFORE the group rows are removed from the cart_rule_group table.
62+
*
63+
* @param int $groupId
64+
*/
65+
public function disableCartRulesThatHadOnlyGroup(int $groupId): bool
66+
{
67+
if (!$this->featureFlagStateChecker->isEnabled(FeatureFlagSettings::FEATURE_FLAG_DISCOUNT)) {
68+
return true;
69+
}
70+
71+
if (empty($groupId)) {
72+
return false;
73+
}
74+
75+
$prefix = _DB_PREFIX_;
76+
$db = Db::getInstance();
77+
78+
$cartRuleIds = $db->executeS(
79+
'SELECT crg.`id_cart_rule`
80+
FROM `' . $prefix . 'cart_rule_group` crg
81+
INNER JOIN `' . $prefix . 'cart_rule` cr ON cr.`id_cart_rule` = crg.`id_cart_rule` AND cr.`group_restriction` = 1
82+
WHERE crg.`id_group` = ' . (int) $groupId . '
83+
AND crg.`id_cart_rule` IN (
84+
SELECT `id_cart_rule` FROM `' . $prefix . 'cart_rule_group` GROUP BY `id_cart_rule` HAVING COUNT(*) = 1
85+
)'
86+
);
87+
88+
if (empty($cartRuleIds)) {
89+
return true;
90+
}
91+
92+
$result = true;
93+
foreach ($cartRuleIds as $row) {
94+
$cartRule = new CartRule((int) $row['id_cart_rule']);
95+
$cartRule->group_restriction = false;
96+
$cartRule->active = false;
97+
$result = $cartRule->update() && $result;
98+
}
99+
100+
return $result;
101+
}
102+
}

src/Adapter/Customer/CommandHandler/DeleteCustomerHandler.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
namespace PrestaShop\PrestaShop\Adapter\Customer\CommandHandler;
88

99
use Customer;
10+
use PrestaShop\PrestaShop\Adapter\CartRule\CartRuleDisablerService;
1011
use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsCommandHandler;
1112
use PrestaShop\PrestaShop\Core\Domain\Customer\Command\DeleteCustomerCommand;
1213
use PrestaShop\PrestaShop\Core\Domain\Customer\CommandHandler\DeleteCustomerHandlerInterface;
@@ -19,6 +20,11 @@
1920
#[AsCommandHandler]
2021
final class DeleteCustomerHandler extends AbstractCustomerHandler implements DeleteCustomerHandlerInterface
2122
{
23+
public function __construct(
24+
private readonly CartRuleDisablerService $cartRuleDisablerService,
25+
) {
26+
}
27+
2228
/**
2329
* {@inheritdoc}
2430
*/
@@ -29,6 +35,11 @@ public function handle(DeleteCustomerCommand $command)
2935

3036
$this->assertCustomerWasFound($customerId, $customer);
3137

38+
// When the discount feature flag is enabled, disable cart rules restricted to this customer
39+
// instead of deleting them, so the merchant can review and re-enable them manually.
40+
// This runs before both the hard-delete and soft-delete paths.
41+
$this->cartRuleDisablerService->disableCartRulesThatHadCustomer($customerId->getValue());
42+
3243
if ($command->getDeleteMethod()->isAllowedToRegisterAfterDelete()) {
3344
$customer->delete();
3445

src/Adapter/Customer/Group/CommandHandler/DeleteCustomerGroupHandler.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace PrestaShop\PrestaShop\Adapter\Customer\Group\CommandHandler;
1010

11+
use PrestaShop\PrestaShop\Adapter\CartRule\CartRuleDisablerService;
1112
use PrestaShop\PrestaShop\Adapter\Customer\Group\Repository\GroupRepository;
1213
use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsCommandHandler;
1314
use PrestaShop\PrestaShop\Core\Domain\Customer\Group\Command\DeleteCustomerGroupCommand;
@@ -18,11 +19,17 @@ class DeleteCustomerGroupHandler implements DeleteCustomerGroupHandlerInterface
1819
{
1920
public function __construct(
2021
private readonly GroupRepository $customerGroupRepository,
22+
private readonly CartRuleDisablerService $cartRuleDisablerService,
2123
) {
2224
}
2325

2426
public function handle(DeleteCustomerGroupCommand $command): void
2527
{
28+
// Disable affected cart rules before removing the group rows from cart_rule_group,
29+
// so the query that finds single-group rules can still run.
30+
$this->cartRuleDisablerService->disableCartRulesThatHadOnlyGroup(
31+
$command->getCustomerGroupId()->getValue()
32+
);
2633
$this->customerGroupRepository->delete($command->getCustomerGroupId());
2734
}
2835
}

src/Adapter/Customer/QueryHandler/SearchCustomersHandler.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,6 @@ public function handle(SearchCustomers $query)
8181
}
8282

8383
foreach ($customersResult as $customerArray) {
84-
if (!$customerArray['active']) {
85-
continue;
86-
}
87-
8884
$customerArray['fullname_and_email'] = sprintf(
8985
'%s %s - %s',
9086
$customerArray['firstname'],

src/Core/Form/IdentifiableObject/DataProvider/DiscountFormDataProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,8 @@ private function getCustomerEligibilityData(DiscountForEditing $discountForEditi
460460
[
461461
'id_customer' => $customerId,
462462
'fullname_and_email' => $fullnameAndEmail,
463+
'active' => (int) $customer->active,
464+
'is_guest' => (int) $customer->is_guest,
463465
],
464466
];
465467
} catch (CustomerNotFoundException $e) {

src/PrestaShopBundle/Form/Admin/Sell/Customer/SearchedCustomerType.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ public function buildForm(FormBuilderInterface $builder, array $options)
2626
->add('fullname_and_email', TextPreviewType::class, [
2727
'label' => false,
2828
])
29+
->add('active', HiddenType::class, [
30+
'label' => false,
31+
])
32+
->add('is_guest', HiddenType::class, [
33+
'label' => false,
34+
])
2935
;
3036
}
3137
}

src/PrestaShopBundle/Form/Admin/Sell/Discount/DiscountCustomerEligibilityChoiceType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
7171
'layout' => EntitySearchInputType::LIST_LAYOUT,
7272
'required' => false,
7373
'disabling_switch' => false,
74-
'exclude_guests' => true,
74+
'exclude_guests' => false,
7575
'constraints' => [
7676
new When(
7777
expression: sprintf(

0 commit comments

Comments
 (0)