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
Migrate "Orders > Shopping Carts" page #35007
Migrate "Orders > Shopping Carts" page #35007
Conversation
Hello @boherm! This is your first pull request on the PrestaShop project. Thank you, and welcome to this Open Source community! |
/** | ||
* @var CartRepository | ||
*/ | ||
private $cartRepository; | ||
|
||
/** | ||
* @var OrderRepository | ||
*/ | ||
private $orderRepository; | ||
|
||
/** | ||
* @param CartRepository $cartRepository | ||
*/ | ||
public function __construct(CartRepository $cartRepository, OrderRepository $orderRepository) | ||
{ | ||
$this->cartRepository = $cartRepository; | ||
$this->orderRepository = $orderRepository; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could type, or even use the construct to define class variables
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep you can use property promotion now, which probably wasn't possible when you started the PR, but I'd say it can be optional if you have time to do it Maybe not blocking
/** | ||
* @var CartRepository | ||
*/ | ||
private $cartRepository; | ||
|
||
/** | ||
* @var OrderRepository | ||
*/ | ||
private $orderRepository; | ||
|
||
/** | ||
* @param CartRepository $cartRepository | ||
*/ | ||
public function __construct(CartRepository $cartRepository, OrderRepository $orderRepository) | ||
{ | ||
$this->cartRepository = $cartRepository; | ||
$this->orderRepository = $orderRepository; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could type, or even use the construct to define class variables
public function handle(DeleteCartCommand $command): void | ||
{ | ||
$order = $this->orderRepository->findByCartId($command->getCartId()); | ||
if ($order) { | ||
throw new CannotDeleteOrderedCartException(sprintf('Cart "%s" with order cannot be deleted.', $command->getCartId()->getValue())); | ||
} | ||
$this->cartRepository->delete($command->getCartId()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part is practically the same as the bulk delete, without the loop. Maybe this can be merged
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would advise to keep them separated ;)
If you forget they look similar, do you think it makes sense the code for bulk delete and code for delete is the same? Aren't they two different things?
I think the implementations coincide but it's just random
https://github.com/PrestaShop/PrestaShop/blob/develop/src/Adapter/Profile/CommandHandler/BulkDeleteProfileHandler.php
https://github.com/PrestaShop/PrestaShop/blob/develop/src/Adapter/Profile/CommandHandler/DeleteProfileHandler.php
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both handlers do the same thing so it's normal they look alike, however each command implementation should remain independent to avoid too much coupling In this case three lines of code that are duplicates are acceptable I think Maintaining a common trait or abstract class would probably cost us more in the long run
src/Adapter/Kpi/AbandonedCartKpi.php
Outdated
/** | ||
* @var TranslatorInterface | ||
*/ | ||
private $translator; | ||
|
||
/** | ||
* @var ConfigurationInterface | ||
*/ | ||
private $configuration; | ||
|
||
/** | ||
* @var LegacyContext | ||
*/ | ||
private $contextAdapter; | ||
|
||
/** | ||
* @var UrlGeneratorInterface | ||
*/ | ||
private $router; | ||
|
||
/** | ||
* @var FeatureFlagRepository | ||
*/ | ||
private $featureFlag; | ||
|
||
/** | ||
* @param TranslatorInterface $translator | ||
* @param ConfigurationInterface $configuration | ||
* @param LegacyContext $contextAdapter | ||
* @param UrlGeneratorInterface $router | ||
* @param FeatureFlagRepository $featureFlag | ||
*/ | ||
public function __construct( | ||
TranslatorInterface $translator, | ||
ConfigurationInterface $configuration, | ||
LegacyContext $contextAdapter, | ||
UrlGeneratorInterface $router, | ||
FeatureFlagRepository $featureFlag | ||
) { | ||
$this->translator = $translator; | ||
$this->configuration = $configuration; | ||
$this->contextAdapter = $contextAdapter; | ||
$this->router = $router; | ||
$this->featureFlag = $featureFlag; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could type, or even use the construct to define class variables
src/Adapter/Kpi/AbandonedCartKpi.php
Outdated
$helper->href = $this->contextAdapter->getAdminLink('AdminCarts', true, [ | ||
'action' => 'filterOnlyAbandonedCarts', | ||
]); | ||
|
||
if ($this->featureFlag->isEnabled(FeatureFlagSettings::FEATURE_FLAG_CARTS)) { | ||
$helper->href = $this->router->generate('admin_carts_index', [ | ||
'cart[filters][status]' => CartStatus::ABANDONED_CART, | ||
]); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$helper->href = $this->contextAdapter->getAdminLink('AdminCarts', true, [ | |
'action' => 'filterOnlyAbandonedCarts', | |
]); | |
if ($this->featureFlag->isEnabled(FeatureFlagSettings::FEATURE_FLAG_CARTS)) { | |
$helper->href = $this->router->generate('admin_carts_index', [ | |
'cart[filters][status]' => CartStatus::ABANDONED_CART, | |
]); | |
} | |
if ($this->featureFlag->isEnabled(FeatureFlagSettings::FEATURE_FLAG_CARTS)) { | |
$helper->href = $this->router->generate('admin_carts_index', [ | |
'cart[filters][status]' => CartStatus::ABANDONED_CART, | |
]); | |
} else { | |
$helper->href = $this->contextAdapter->getAdminLink('AdminCarts', true, [ | |
'action' => 'filterOnlyAbandonedCarts', | |
]); | |
} |
@@ -536,3 +536,11 @@ services: | |||
$languageId: '@=service("prestashop.adapter.legacy.context").getContext().language.id' | |||
$connection: '@doctrine.dbal.default_connection' | |||
$dbPrefix: '%database_prefix%' | |||
|
|||
prestashop.core.grid.query.builder.cart: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use FQCN instead
- '@prestashop.core.grid.query.doctrine_query_parser' | ||
- 'cart' | ||
|
||
prestashop.core.grid.data.factory.cart_decorator: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use FQCN instead
@@ -428,3 +428,14 @@ services: | |||
calls: | |||
- [ setTranslator, [ '@translator' ] ] | |||
deprecated: ~ | |||
|
|||
prestashop.core.grid.definition.factory.cart: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use FQCN instead
- '@=service("prestashop.adapter.legacy.context").getContext().language.date_format_full' | ||
- '@prestashop.adapter.shop.context' | ||
- '@=service("prestashop.adapter.multistore_feature").isActive()' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is better to do these operations in the class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work 😉
I provided little things to improve/fix but it's very minor
@@ -0,0 +1,51 @@ | |||
/** | |||
* vCopyright since 2007 PrestaShop SA and Contributors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* vCopyright since 2007 PrestaShop SA and Contributors | |
* Copyright since 2007 PrestaShop SA and Contributors |
Little typo, I guess a copy-paste missclick 😄
public function handle(DeleteCartCommand $command): void | ||
{ | ||
$order = $this->orderRepository->findByCartId($command->getCartId()); | ||
if ($order) { | ||
throw new CannotDeleteOrderedCartException(sprintf('Cart "%s" with order cannot be deleted.', $command->getCartId()->getValue())); | ||
} | ||
$this->cartRepository->delete($command->getCartId()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would advise to keep them separated ;)
If you forget they look similar, do you think it makes sense the code for bulk delete and code for delete is the same? Aren't they two different things?
I think the implementations coincide but it's just random
https://github.com/PrestaShop/PrestaShop/blob/develop/src/Adapter/Profile/CommandHandler/BulkDeleteProfileHandler.php
https://github.com/PrestaShop/PrestaShop/blob/develop/src/Adapter/Profile/CommandHandler/DeleteProfileHandler.php
src/Adapter/Kpi/AbandonedCartKpi.php
Outdated
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function render() | ||
{ | ||
$translator = Context::getContext()->getTranslator(); | ||
$dateFormat = $this->contextAdapter->getLanguage()->date_format_lite; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that we have the modern contexts you should prefer using them so you can inject LanguageContext
instead here, date_format_lite
is synonym to LanguageContext->getDateFormat()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @boherm
A few comments here and there but it's looking almost finished, the suggestions from Morgan about property promotion would be a nice bonus but I won't block only for that personally
/** | ||
* @var CartRepository | ||
*/ | ||
private $cartRepository; | ||
|
||
/** | ||
* @var OrderRepository | ||
*/ | ||
private $orderRepository; | ||
|
||
/** | ||
* @param CartRepository $cartRepository | ||
*/ | ||
public function __construct(CartRepository $cartRepository, OrderRepository $orderRepository) | ||
{ | ||
$this->cartRepository = $cartRepository; | ||
$this->orderRepository = $orderRepository; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep you can use property promotion now, which probably wasn't possible when you started the PR, but I'd say it can be optional if you have time to do it Maybe not blocking
* Deletes cart in bulk action using legacy object model | ||
*/ | ||
#[AsCommandHandler] | ||
final class BulkDeleteCartHandler implements BulkDeleteCartHandlerInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You couldn't know about it but a class as introduced for bulk handlers AbstractBulkCommandHandler
there is an example of usage here:
class BulkDeleteAliasHandler extends AbstractBulkCommandHandler implements BulkDeleteAliasHandlerInterface |
It has two advantages:
- it mutualizes the code to handle bulk commands which is almost always the same except for the specific part that performs the single action
- it forces using an exception that implements
BulkCommandExceptionInterface
this interface allows handling an exception for each entity that failed thus allowing more accurate error message in the UX
public function handle(DeleteCartCommand $command): void | ||
{ | ||
$order = $this->orderRepository->findByCartId($command->getCartId()); | ||
if ($order) { | ||
throw new CannotDeleteOrderedCartException(sprintf('Cart "%s" with order cannot be deleted.', $command->getCartId()->getValue())); | ||
} | ||
$this->cartRepository->delete($command->getCartId()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both handlers do the same thing so it's normal they look alike, however each command implementation should remain independent to avoid too much coupling In this case three lines of code that are duplicates are acceptable I think Maintaining a common trait or abstract class would probably cost us more in the long run
* | ||
* @throws CoreException | ||
*/ | ||
public function findByCartId(CartId $cartId): ?Order |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public function findByCartId(CartId $cartId): ?Order | |
public function getByCartId(CartId $cartId): Order |
To be more consistent with the usual get
method in the object model repositories Also, instead of returning null maybe you should throw an OrderNotFoundException
, also to be more consistent with the get
behaviour
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either that or you could implement a public function getOrderIdByCartId(CartId $cartId): ?OrderId
method (this one could have a nullable return), which would match more with the usage you have (check if an order is associated to the cart) and it prevents fetching the whole Order
object which you don't need in your delete handlers
src/Adapter/Kpi/AbandonedCartKpi.php
Outdated
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function render() | ||
{ | ||
$translator = Context::getContext()->getTranslator(); | ||
$dateFormat = $this->contextAdapter->getLanguage()->date_format_lite; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that we have the modern contexts you should prefer using them so you can inject LanguageContext
instead here, date_format_lite
is synonym to LanguageContext->getDateFormat()
_controller: PrestaShopBundle\Controller\Admin\Sell\Order\CartController::indexAction | ||
_legacy_controller: AdminCarts | ||
_legacy_link: AdminCarts | ||
_legacy_feature_flag: carts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's good to add the feature flag on the new routes, but you probably should also add it on the existing ones in this file
prestashop.adapter.cart.command_handler.delete_cart_handler: | ||
class: 'PrestaShop\PrestaShop\Adapter\Cart\CommandHandler\DeleteCartHandler' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prestashop.adapter.cart.command_handler.delete_cart_handler: | |
class: 'PrestaShop\PrestaShop\Adapter\Cart\CommandHandler\DeleteCartHandler' | |
PrestaShop\PrestaShop\Adapter\Cart\CommandHandler\DeleteCartHandler: |
We favor the FQCN notation now, even for private services and handlers
prestashop.adapter.cart.command_handler.bulk_delete_cart_handler: | ||
class: 'PrestaShop\PrestaShop\Adapter\Cart\CommandHandler\BulkDeleteCartHandler' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prestashop.adapter.cart.command_handler.bulk_delete_cart_handler: | |
class: 'PrestaShop\PrestaShop\Adapter\Cart\CommandHandler\BulkDeleteCartHandler' | |
PrestaShop\PrestaShop\Adapter\Cart\CommandHandler\BulkDeleteCartHandler: |
*/ | ||
public function __construct( | ||
HookDispatcherInterface $hookDispatcher, | ||
string $contextDateFormat, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should inject LanguageContext
instead
Then cart "dummy_cart1" should exist because cart is already ordered | ||
And cart "dummy_cart2" should exist | ||
And cart "dummy_cart3" should exist |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually shows an issue here, the bulk action shouldn't stop because one of the actions failed It's supposed to keep doing the delete on the whole list and store if any of them failed
But this comment is actually in line with my previous suggestions on the bulk handler, the suggested abstract class is actually here to help implementing this kind of behavior and we'd like to make it generic on all bulk actions for consistency
- Add alignement system in column - Add `badge_type_field` to use column in grid to select badge type directly and no only color (like `color_field` for instance)
- Use DI instead of `Context` - Add purple color - Create kpi_row factory for Carts page kpi
- Add index action page - Add solo/bulk delete actions - Add export into csv
Add cart JS grid query grid
fix repository
- Add new column for display & search status - Fix AbandonedCartKpi to go directly into abandoned cart fix grid improvement
6e3e69b
to
c4f68ae
Compare
0965d59
to
236e4c7
Compare
Some tests before merging: https://github.com/boherm/ga.tests.ui.pr/actions/runs/7841541053 |
make admin-new-theme
!First, active
Carts index
feature flag, and then navigate into Orders -> Carts page.You can filter data, delete not already ordered carts one by one or by bulk actions.
Multistore mode is supported, and in that case we add Shop column into grid (only for all / group context).
Warning:
For now new Kpi use legacy to retrieve data in all already migrated pages!