Skip to content

Commit c9ae935

Browse files
author
Sergii Kovalenko
committed
MAGETWO-60756: "Uses per Coupon" limit does not work for auto generated coupons
--fix coupling
1 parent 043be66 commit c9ae935

File tree

4 files changed

+299
-15
lines changed

4 files changed

+299
-15
lines changed

app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,36 @@
66
*/
77
namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote;
88

9+
use Magento\Framework\App\ObjectManager;
10+
use Magento\SalesRule\Model\CouponGenerator;
11+
912
class Generate extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote
1013
{
14+
/**
15+
* @var CouponGenerator
16+
*/
17+
private $couponGenerator;
18+
19+
/**
20+
* Generate constructor.
21+
* @param \Magento\Backend\App\Action\Context $context
22+
* @param \Magento\Framework\Registry $coreRegistry
23+
* @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
24+
* @param \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter
25+
* @param CouponGenerator|null $couponGenerator
26+
*/
27+
public function __construct(
28+
\Magento\Backend\App\Action\Context $context,
29+
\Magento\Framework\Registry $coreRegistry,
30+
\Magento\Framework\App\Response\Http\FileFactory $fileFactory,
31+
\Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter,
32+
CouponGenerator $couponGenerator = null
33+
) {
34+
parent::__construct($context, $coreRegistry, $fileFactory, $dateFilter);
35+
$this->couponGenerator = $couponGenerator ?:
36+
$this->_objectManager->get(CouponGenerator::class);
37+
}
38+
1139
/**
1240
* Generate Coupons action
1341
*
@@ -22,7 +50,6 @@ public function execute()
2250
$result = [];
2351
$this->_initRule();
2452

25-
/** @var $rule \Magento\SalesRule\Model\Rule */
2653
$rule = $this->_coreRegistry->registry(\Magento\SalesRule\Model\RegistryConstants::CURRENT_SALES_RULE);
2754

2855
if (!$rule->getId()) {
@@ -35,18 +62,13 @@ public function execute()
3562
$data = $inputFilter->getUnescaped();
3663
}
3764

38-
/** @var $generator \Magento\SalesRule\Model\Coupon\Massgenerator */
39-
$generator = $this->_objectManager->get(\Magento\SalesRule\Model\Coupon\Massgenerator::class);
40-
if (!$generator->validateData($data)) {
41-
$result['error'] = __('Invalid data provided');
42-
} else {
43-
$generator->setData($data);
44-
$generator->generatePool();
45-
$generated = $generator->getGeneratedCount();
46-
$this->messageManager->addSuccess(__('%1 coupon(s) have been generated.', $generated));
47-
$this->_view->getLayout()->initMessages();
48-
$result['messages'] = $this->_view->getLayout()->getMessagesBlock()->getGroupedHtml();
49-
}
65+
$couponCodes = $this->couponGenerator->generateCodes($data);
66+
$generated = count($couponCodes);
67+
$this->messageManager->addSuccess(__('%1 coupon(s) have been generated.', $generated));
68+
$this->_view->getLayout()->initMessages();
69+
$result['messages'] = $this->_view->getLayout()->getMessagesBlock()->getGroupedHtml();
70+
} catch (\Magento\Framework\Exception\InputException $inputException) {
71+
$result['error'] = __('Invalid data provided');
5072
} catch (\Magento\Framework\Exception\LocalizedException $e) {
5173
$result['error'] = $e->getMessage();
5274
} catch (\Exception $e) {
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
/**
3+
* Copyright © 2013-2017 Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\SalesRule\Model;
7+
8+
/**
9+
* Allows to generate a pool of coupon codes.
10+
*
11+
* Generated coupon code - auto generated string, which is used on checkout in order to get
12+
* discount (fixed or in percents) on whole customer shopping cart or on items in this shopping cart.
13+
* Class was added due to Backward Compatibility and is used as proxy to:
14+
* @see \Magento\SalesRule\Model\Service\CouponManagementService
15+
*/
16+
class CouponGenerator
17+
{
18+
/**
19+
* Map keys in old and new services
20+
*
21+
* Controller was used as old service
22+
* @see \Magento\SalesRule\Controller\Adminhtml\Promo\Quote\Generate
23+
* - key = key in new service
24+
* - value = key in old service
25+
*
26+
* @var array
27+
*/
28+
private $keyMap = [
29+
'quantity' => 'qty'
30+
];
31+
32+
/**
33+
* @var Service\CouponManagementService
34+
*/
35+
private $couponManagementService;
36+
37+
/**
38+
* @var \Magento\SalesRule\Api\Data\CouponGenerationSpecInterfaceFactory
39+
*/
40+
private $generationSpecFactory;
41+
42+
/**
43+
* All objects should be injected through constructor, because we need to have working service already
44+
* after it initializing
45+
*
46+
* @param Service\CouponManagementService $couponManagementService
47+
* @param \Magento\SalesRule\Api\Data\CouponGenerationSpecInterfaceFactory $generationSpecFactory
48+
*/
49+
public function __construct(
50+
\Magento\SalesRule\Model\Service\CouponManagementService $couponManagementService,
51+
\Magento\SalesRule\Api\Data\CouponGenerationSpecInterfaceFactory $generationSpecFactory
52+
) {
53+
$this->couponManagementService = $couponManagementService;
54+
$this->generationSpecFactory = $generationSpecFactory;
55+
}
56+
57+
/**
58+
* Generate a pool of generated coupon codes
59+
*
60+
* This method is used as proxy, due to high coupling in constructor
61+
* @see \Magento\SalesRule\Controller\Adminhtml\Promo\Quote\Generate
62+
* In order to generate valid coupon codes, we need to initialize DTO object and run service.
63+
* @see \Magento\SalesRule\Api\Data\CouponGenerationSpecInterface -> DTO object
64+
*
65+
* @param array $parameters
66+
* @return string[]
67+
*/
68+
public function generateCodes(array $parameters)
69+
{
70+
$couponSpecData = $this->convertCouponSpecData($parameters);
71+
$couponSpec = $this->generationSpecFactory->create(['data' => $couponSpecData]);
72+
return $this->couponManagementService->generate($couponSpec);
73+
}
74+
75+
/**
76+
* We should map old values to new one
77+
* We need to do this, as new service with another key names was added
78+
*
79+
* @param array $data
80+
* @return array
81+
*/
82+
private function convertCouponSpecData(array $data)
83+
{
84+
foreach ($this->keyMap as $mapKey => $mapValue) {
85+
$data[$mapKey] = isset($data[$mapValue]) ? $data[$mapValue] : null;
86+
}
87+
88+
return $data;
89+
}
90+
}

app/code/Magento/SalesRule/Model/Service/CouponManagementService.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
*/
66
namespace Magento\SalesRule\Model\Service;
77

8-
use Magento\SalesRule\Model\Coupon;
9-
108
/**
119
* Coupon management service class
1210
*
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
<?php
2+
/**
3+
* Copyright © 2013-2017 Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\SalesRule\Test\Unit\Controller\Adminhtml\Promo\Quote;
7+
8+
use Magento\Framework\App\ObjectManager;
9+
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
10+
use Magento\SalesRule\Model\CouponGenerator;
11+
12+
/**
13+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
14+
*/
15+
class GenerateTest extends \PHPUnit_Framework_TestCase
16+
{
17+
/** @var \Magento\SalesRule\Controller\Adminhtml\Promo\Quote\Generate */
18+
protected $model;
19+
20+
/** @var ObjectManagerHelper */
21+
protected $objectManagerHelper;
22+
23+
/** @var \Magento\Backend\App\Action\Context|\PHPUnit_Framework_MockObject_MockObject */
24+
protected $contextMock;
25+
26+
/** @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject */
27+
protected $registryMock;
28+
29+
/** @var \PHPUnit_Framework_MockObject_MockObject */
30+
private $requestMock;
31+
32+
/** @var \PHPUnit_Framework_MockObject_MockObject */
33+
private $messageManager;
34+
35+
/** @var \PHPUnit_Framework_MockObject_MockObject */
36+
private $responseMock;
37+
38+
/** @var \Magento\Framework\App\Response\Http\FileFactory|\PHPUnit_Framework_MockObject_MockObject */
39+
protected $fileFactoryMock;
40+
41+
/** @var \PHPUnit_Framework_MockObject_MockObject */
42+
private $view;
43+
44+
/** @var \Magento\Framework\Stdlib\DateTime\Filter\Date|\PHPUnit_Framework_MockObject_MockObject */
45+
protected $dateMock;
46+
47+
/** @var ObjectManager | \PHPUnit_Framework_MockObject_MockObject */
48+
private $objectManagerMock;
49+
50+
/** @var CouponGenerator | \PHPUnit_Framework_MockObject_MockObject */
51+
private $couponGenerator;
52+
53+
protected function setUp()
54+
{
55+
$this->contextMock = $this->getMockBuilder(\Magento\Backend\App\Action\Context::class)
56+
->disableOriginalConstructor()
57+
->getMock();
58+
$this->requestMock = $this
59+
->getMockBuilder(\Magento\Framework\App\Request\Http::class)
60+
->disableOriginalConstructor()
61+
->getMock();
62+
$this->responseMock = $this
63+
->getMockBuilder(\Magento\Framework\App\Response\Http::class)
64+
->disableOriginalConstructor()
65+
->getMock();
66+
$this->messageManager = $this->getMock(\Magento\Framework\Message\ManagerInterface::class);
67+
$this->objectManagerMock = $this->getMockBuilder(ObjectManager::class)
68+
->disableOriginalConstructor()
69+
->getMock();
70+
$this->view = $this->getMock(\Magento\Framework\App\ViewInterface::class);
71+
$this->contextMock->expects($this->once())
72+
->method('getView')
73+
->willReturn($this->view);
74+
$this->contextMock->expects($this->once())
75+
->method('getMessageManager')
76+
->willReturn($this->messageManager);
77+
$this->contextMock->expects($this->once())
78+
->method('getRequest')
79+
->willReturn($this->requestMock);
80+
$this->contextMock->expects($this->once())
81+
->method('getObjectManager')
82+
->willReturn($this->objectManagerMock);
83+
$this->contextMock->expects($this->once())
84+
->method('getResponse')
85+
->willReturn($this->responseMock);
86+
$this->registryMock = $this->getMockBuilder(\Magento\Framework\Registry::class)
87+
->disableOriginalConstructor()
88+
->getMock();
89+
$this->fileFactoryMock = $this->getMockBuilder(\Magento\Framework\App\Response\Http\FileFactory::class)
90+
->disableOriginalConstructor()
91+
->getMock();
92+
$this->dateMock = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\Filter\Date::class)
93+
->disableOriginalConstructor()
94+
->getMock();
95+
$this->couponGenerator = $this->getMockBuilder(CouponGenerator::class)
96+
->disableOriginalConstructor()
97+
->getMock();
98+
99+
$this->objectManagerHelper = new ObjectManagerHelper($this);
100+
$this->model = $this->objectManagerHelper->getObject(
101+
\Magento\SalesRule\Controller\Adminhtml\Promo\Quote\Generate::class,
102+
[
103+
'context' => $this->contextMock,
104+
'coreRegistry' => $this->registryMock,
105+
'fileFactory' => $this->fileFactoryMock,
106+
'dateFilter' => $this->dateMock,
107+
'couponGenerator' => $this->couponGenerator
108+
]
109+
);
110+
}
111+
112+
public function testExecute()
113+
{
114+
$helperData = $this->getMockBuilder(\Magento\Framework\Json\Helper\Data::class)
115+
->disableOriginalConstructor()
116+
->getMock();
117+
$this->objectManagerMock->expects($this->any())
118+
->method('get')
119+
->with(\Magento\Framework\Json\Helper\Data::class)
120+
->willReturn($helperData);
121+
$requestData = [
122+
'qty' => 2,
123+
'length' => 10,
124+
'rule_id' => 1
125+
];
126+
$this->requestMock->expects($this->once())
127+
->method('isAjax')
128+
->willReturn(true);
129+
$ruleMock = $this->getMockBuilder(\Magento\SalesRule\Model\Rule::class)
130+
->disableOriginalConstructor()
131+
->getMock();
132+
$this->registryMock->expects($this->once())
133+
->method('registry')
134+
->willReturn($ruleMock);
135+
$ruleMock->expects($this->once())
136+
->method('getId')
137+
->willReturn(1);
138+
$this->requestMock->expects($this->once())
139+
->method('getParams')
140+
->willReturn($requestData);
141+
$this->couponGenerator->expects($this->once())
142+
->method('generateCodes')
143+
->with($requestData)
144+
->willReturn(['some_data', 'some_data_2']);
145+
$this->messageManager->expects($this->once())
146+
->method('addSuccess');
147+
$this->responseMock->expects($this->once())
148+
->method('representJson')
149+
->with();
150+
$helperData->expects($this->once())
151+
->method('jsonEncode')
152+
->with([
153+
'messages' => __('%1 coupon(s) have been generated.', 2)
154+
]);
155+
$layout = $this->getMockBuilder(\Magento\Framework\View\Layout::class)
156+
->disableOriginalConstructor()
157+
->getMock();
158+
$this->view->expects($this->any())
159+
->method('getLayout')
160+
->willReturn($layout);
161+
$messageBlock = $this->getMockBuilder(\Magento\Framework\View\Element\Messages::class)
162+
->disableOriginalConstructor()
163+
->getMock();
164+
$layout->expects($this->once())
165+
->method('initMessages');
166+
$layout->expects($this->once())
167+
->method('getMessagesBlock')
168+
->willReturn($messageBlock);
169+
$messageBlock->expects($this->once())
170+
->method('getGroupedHtml')
171+
->willReturn(__('%1 coupon(s) have been generated.', 2));
172+
$this->model->execute();
173+
}
174+
}

0 commit comments

Comments
 (0)