Merge pull request #4364 from TheMadeleine/product-not-softdeletable-implementation

[Product] Remove softdeletable from Product
This commit is contained in:
Paweł Jędrzejewski 2016-03-15 15:18:11 +01:00
commit 2a3dff8efd
40 changed files with 906 additions and 245 deletions

View file

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20160309144959 extends AbstractMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE sylius_product DROP deleted_at');
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE sylius_product ADD deleted_at DATETIME DEFAULT NULL');
}
}

View file

@ -41,7 +41,9 @@
<service id="sylius.behat.context.domain.product" class="%sylius.behat.context.domain.product.class%" scope="scenario">
<argument type="service" id="sylius.behat.shared_storage" container="symfony" />
<argument type="service" id="sylius.repository.product_variant" container="symfony"/>
<argument type="service" id="sylius.repository.product" container="symfony" />
<argument type="service" id="sylius.repository.product_variant" container="symfony" />
<argument type="service" id="sylius.repository.product_review" container="symfony" />
<tag name="sylius.behat.context" />
</service>
</services>

View file

@ -20,6 +20,7 @@
<parameter key="sylius.behat.context.setup.security.class">Sylius\Behat\Context\Setup\SecurityContext</parameter>
<parameter key="sylius.behat.context.setup.user.class">Sylius\Behat\Context\Setup\UserContext</parameter>
<parameter key="sylius.behat.context.setup.product.class">Sylius\Behat\Context\Setup\ProductContext</parameter>
<parameter key="sylius.behat.context.setup.product_review.class">Sylius\Behat\Context\Setup\ProductReviewContext</parameter>
<parameter key="sylius.behat.context.setup.promotion.class">Sylius\Behat\Context\Setup\PromotionContext</parameter>
<parameter key="sylius.behat.context.setup.shipping.class">Sylius\Behat\Context\Setup\ShippingContext</parameter>
<parameter key="sylius.behat.context.setup.zone.class">Sylius\Behat\Context\Setup\ZoneContext</parameter>
@ -88,6 +89,13 @@
<tag name="sylius.behat.context" />
</service>
<service id="sylius.behat.context.setup.product_review" class="%sylius.behat.context.setup.product_review.class%" scope="scenario">
<argument type="service" id="sylius.behat.shared_storage" container="symfony" />
<argument type="service" id="sylius.factory.product_review" container="symfony" />
<argument type="service" id="sylius.repository.product_review" container="symfony" />
<tag name="sylius.behat.context" />
</service>
<service id="sylius.behat.context.setup.promotion" class="%sylius.behat.context.setup.promotion.class%" scope="scenario">
<argument type="service" id="sylius.behat.shared_storage" container="symfony" />
<argument type="service" id="sylius.factory.promotion_action" container="symfony" />

View file

@ -70,7 +70,10 @@
</service>
<service id="sylius.behat.context.ui.product" class="%sylius.behat.context.ui.product.class%" scope="scenario">
<argument type="service" id="sylius.behat.shared_storage" container="symfony" />
<argument type="service" id="sylius.behat.page.product_show" />
<argument type="service" id="sylius.behat.page.admin.product_show" />
<argument type="service" id="sylius.behat.page.admin.product_index" />
<tag name="sylius.behat.context" />
</service>

View file

@ -13,6 +13,9 @@
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sylius.behat.page.admin.product_show.class">Sylius\Behat\Page\Admin\Product\ShowPage</parameter>
<parameter key="sylius.behat.page.admin.product_index.class">Sylius\Behat\Page\Admin\Product\IndexPage</parameter>
<parameter key="sylius.behat.page.cart_summary.class">Sylius\Behat\Page\Cart\CartSummaryPage</parameter>
<parameter key="sylius.behat.page.product_show.class">Sylius\Behat\Page\Product\ProductShowPage</parameter>
<parameter key="sylius.behat.page.customer_show.class">Sylius\Behat\Page\Customer\CustomerShowPage</parameter>
@ -38,6 +41,11 @@
<parameter key="sylius.behat.page.admin.locale.index.class">%sylius.behat.page.admin.crud.index.class%</parameter>
</parameters>
<services>
<service id="sylius.behat.page.admin.product_show" class="%sylius.behat.page.admin.product_show.class%" parent="sylius.behat.symfony_page" scope="scenario" public="false" />
<service id="sylius.behat.page.admin.product_index" class="%sylius.behat.page.admin.product_index.class%" parent="sylius.behat.symfony_page" scope="scenario" public="false">
<argument type="service" id="sylius.behat.table_manipulator" />
</service>
<service id="sylius.behat.page.cart_summary" class="%sylius.behat.page.cart_summary.class%" parent="sylius.behat.symfony_page" scope="scenario" public="false" />
<service id="sylius.behat.page.product_show" class="%sylius.behat.page.product_show.class%" parent="sylius.behat.symfony_page" scope="scenario" public="false" />
<service id="sylius.behat.page.customer_show" class="%sylius.behat.page.customer_show.class%" parent="sylius.behat.symfony_page" scope="scenario" public="false" />

View file

@ -39,6 +39,7 @@ imports:
- suites/ui/theme.yml
- suites/ui/user.yml
- suites/ui/localization.yml
- suites/ui/product.yml
- suites/domain/order.yml
- suites/domain/product.yml

View file

@ -20,6 +20,8 @@ default:
- sylius.behat.context.setup.payment
- sylius.behat.context.setup.shipping
- sylius.behat.context.setup.order
- sylius.behat.context.setup.product_review
- sylius.behat.context.setup.security
- sylius.behat.context.domain.product

View file

@ -0,0 +1,25 @@
# This file is part of the Sylius package.
# (c) Paweł Jędrzejewski
default:
suites:
ui_product:
contexts_as_services:
- sylius.behat.context.hook.doctrine_orm
- sylius.behat.context.transform.zone
- sylius.behat.context.transform.lexical
- sylius.behat.context.transform.shared_storage
- sylius.behat.context.transform.product
- sylius.behat.context.setup.channel
- sylius.behat.context.setup.geographical
- sylius.behat.context.setup.product
- sylius.behat.context.setup.zone
- sylius.behat.context.setup.product_review
- sylius.behat.context.setup.security
- sylius.behat.context.ui.product
filters:
tags: @product && @ui

View file

@ -82,14 +82,6 @@ Feature: Orders management
Then I should be on the order index page
And I should see "Order has been successfully deleted"
Scenario: Order integrity is preserved after deleting a product
Given I have deleted the product "Mug"
And I go to the order index page
When I click "Details" near "#000000001"
Then I should be viewing order with number "000000001"
And I should see "Mug"
And I should see "Total: 14.74"
Scenario: Accessing the order details page from list
Given I am on the order index page
When I click "Details" near "#000000001"

View file

@ -9,18 +9,19 @@ Feature: Deleting a product
And the store has a product "Toyota GT86 model"
And this product has "1:43" variant priced at "15.00"
And this product has one review
And I am logged in as administrator
@todo
@ui @javascript
Scenario: Deleted product disappears from the product catalog
When I delete the "Toyota GT86 model" product
Then this product should not exist in the product catalog
@todo
@domain
Scenario: Deleted product variants disappear from the product catalog
When I delete the "Toyota GT86 model" product
Then there should be no "1:43" variant of this product in the product catalog
Then there should be no variants of this product in the product catalog
@todo
@domain
Scenario: Deleted product reviews disappear from the product catalog
When I delete the "Toyota GT86 model" product
Then there should be no reviews of this product

View file

@ -0,0 +1,20 @@
@product
Feature: Prevent deletion of purchased product
In order to maintain proper order history
As an Administrator
I want to be prevented from deleting purchased products
Background:
Given the store operates on a single channel in "France"
And the store ships everywhere for free
And the store allows paying with "Cash on Delivery"
And the store has a product "Toyota GT86 model"
And the customer "john.doe@gmail.com" placed an order "#00000027"
And the customer chose "Free" shipping method to "France" with "Cash on Delivery" payment
And the customer bought single "Toyota GT86 model"
@domain
Scenario: Purchased product cannot be deleted
When I try to delete the "Toyota GT86 model" product
Then I should be notified that this product is in use and cannot be deleted
And this product should still exist in the product catalog

View file

@ -13,12 +13,15 @@ namespace Sylius\Behat\Context\Domain;
use Behat\Behat\Context\Context;
use Doctrine\DBAL\DBALException;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\Repository\ProductVariantRepositoryInterface;
use Sylius\Component\Core\Test\Services\SharedStorageInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
/**
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
* @author Magdalena Banasiak <magdalena.banasiak@lakion.com>
*/
final class ProductContext implements Context
{
@ -27,21 +30,37 @@ final class ProductContext implements Context
*/
private $sharedStorage;
/**
* @var RepositoryInterface
*/
private $productRepository;
/**
* @var ProductVariantRepositoryInterface
*/
private $productVariantRepository;
/**
* @var RepositoryInterface
*/
private $reviewRepository;
/**
* @param SharedStorageInterface $sharedStorage
* @param RepositoryInterface $productRepository
* @param ProductVariantRepositoryInterface $productVariantRepository
* @param RepositoryInterface $reviewRepository
*/
public function __construct(
SharedStorageInterface $sharedStorage,
ProductVariantRepositoryInterface $productVariantRepository
RepositoryInterface $productRepository,
ProductVariantRepositoryInterface $productVariantRepository,
RepositoryInterface $reviewRepository
) {
$this->sharedStorage = $sharedStorage;
$this->productRepository = $productRepository;
$this->productVariantRepository = $productVariantRepository;
$this->reviewRepository = $reviewRepository;
}
/**
@ -66,9 +85,21 @@ final class ProductContext implements Context
}
/**
* @When I should be notified that this variant is in use and cannot be deleted
* @When /^I try to delete the ("([^"]+)" product)$/
*/
public function iShouldBeNotifiedThatThisVariantIsInUseAndCannotBeDeleted()
public function iTryToDeleteTheProduct(ProductInterface $product)
{
try {
$this->productRepository->remove($product);
} catch (DBALException $exception) {
$this->sharedStorage->set('last_exception', $exception);
}
}
/**
* @When /^I should be notified that this (?:variant|product) is in use and cannot be deleted$/
*/
public function iShouldBeNotifiedThatThisProductVariantIsInUseAndCannotBeDeleted()
{
expect($this->sharedStorage->get('last_exception'))->toHaveType(DBALException::class);
}
@ -85,7 +116,7 @@ final class ProductContext implements Context
}
/**
* @Then /^([^"]+) should still exist in the product catalog$/
* @Then /^(this variant) should still exist in the product catalog$/
*/
public function productVariantShouldExistInTheProductCatalog(ProductVariantInterface $productVariant)
{
@ -93,4 +124,43 @@ final class ProductContext implements Context
expect($productVariant)->toNotBe(null);
}
/**
* @Then /^(this product) should still exist in the product catalog$/
*/
public function productShouldExistInTheProductCatalog(ProductInterface $product)
{
$product = $this->productRepository->find($product->getId());
expect($product)->toNotBe(null);
}
/**
* @When /^I delete the ("[^"]+" product)$/
*/
public function iDeleteTheProduct(ProductInterface $product)
{
try {
$this->sharedStorage->set('deleted_product', $product);
$this->productRepository->remove($product);
} catch (DBALException $exception) {
$this->sharedStorage->set('last_exception', $exception);
}
}
/**
* @Then /^there should be no reviews of (this product)$/
*/
public function thereAreNoProductReviews(ProductInterface $product)
{
expect($this->reviewRepository->findBy(['reviewSubject' => $product]))->toBe([]);
}
/**
* @Then /^there should be no variants of (this product) in the product catalog$/
*/
public function thereAreNoVariants(ProductInterface $product)
{
expect($this->productVariantRepository->findBy(['object' => $product]))->toBe([]);
}
}

View file

@ -25,6 +25,7 @@ use Sylius\Component\Taxation\Model\TaxCategoryInterface;
/**
* @author Arkadiusz Krakowiak <arkadiusz.krakowiak@lakion.com>
* @author Mateusz Zalewski <mateusz.zalewski@lakion.com>
* @author Magdalena Banasiak <magdalena.banasiak@lakion.com>
*/
final class ProductContext implements Context
{
@ -80,7 +81,9 @@ final class ProductContext implements Context
*/
public function storeHasAProductPricedAt($productName, $price = 0)
{
/** @var ProductInterface $product */
$product = $this->productFactory->createNew();
$product->setName($productName);
$product->setPrice($price);
$product->setDescription('Awesome '.$productName);
@ -93,6 +96,25 @@ final class ProductContext implements Context
$this->sharedStorage->set('product', $product);
}
/**
* @Given /^the (product "[^"]+") has "([^"]+)" variant priced at ("[^"]+")$/
* @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+")$/
*/
public function theProductHasVariantPricedAt(ProductInterface $product, $productVariantName, $price)
{
/** @var ProductVariantInterface $variant */
$variant = $this->productVariantFactory->createNew();
$variant->setPresentation($productVariantName);
$variant->setPrice($price);
$variant->setProduct($product);
$product->addVariant($variant);
$this->objectManager->flush();
$this->sharedStorage->set('variant', $variant);
}
/**
* @Given /^there is product "([^"]+)" available in ((?:this|that|"[^"]+") channel)$/
*/
@ -100,6 +122,7 @@ final class ProductContext implements Context
{
/** @var ProductInterface $product */
$product = $this->productFactory->createNew();
$product->setName($productName);
$product->setPrice(0);
$product->setDescription('Awesome ' . $productName);
@ -123,7 +146,9 @@ final class ProductContext implements Context
public function itComesInTheFollowingVariations(ProductInterface $product, TableNode $table)
{
foreach ($table->getHash() as $variantHash) {
/** @var ProductVariantInterface $variant */
$variant = $this->productVariantFactory->createNew();
$variant->setPresentation($variantHash['name']);
$variant->setPrice($this->getPriceFromString(str_replace(['$', '€', '£'], '', $variantHash['price'])));
$variant->setProduct($product);
@ -142,23 +167,6 @@ final class ProductContext implements Context
$this->objectManager->flush($productVariant);
}
/**
* @Given /^the (product "[^"]+") has "([^"]+)" variant priced at ("[^"]+")$/
*/
public function theProductHasVariantPricedAt(ProductInterface $product, $productVariantName, $price)
{
$variant = $this->productVariantFactory->createNew();
$variant->setPresentation($productVariantName);
$variant->setPrice($price);
$variant->setProduct($product);
$product->addVariant($variant);
$this->objectManager->flush();
$this->sharedStorage->set('variant', $variant);
}
/**
* @param string $price
*

View file

@ -0,0 +1,70 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Behat\Context\Setup;
use Behat\Behat\Context\Context;
use Doctrine\Common\Persistence\ObjectManager;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Test\Services\SharedStorageInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
/**
* @author Magdalena Banasiak <magdalena.banasiak@lakion.com>
*/
final class ProductReviewContext implements Context
{
/**
* @var SharedStorageInterface
*/
private $sharedStorage;
/**
* @var FactoryInterface
*/
private $productReviewFactory;
/**
* @var RepositoryInterface
*/
private $productReviewRepository;
/**
* @param SharedStorageInterface $sharedStorage
* @param FactoryInterface $productReviewFactory
* @param RepositoryInterface $productReviewRepository
*/
public function __construct(
SharedStorageInterface $sharedStorage,
FactoryInterface $productReviewFactory,
RepositoryInterface $productReviewRepository
) {
$this->sharedStorage = $sharedStorage;
$this->productReviewFactory = $productReviewFactory;
$this->productReviewRepository = $productReviewRepository;
}
/**
* @Given /^(this product) has one review$/
*/
public function productHasAReview(ProductInterface $product)
{
$review = $this->productReviewFactory->createNew();
$review->setTitle('title');
$review->setRating(5);
$review->setReviewSubject($product);
$product->addReview($review);
$this->productReviewRepository->add($review);
}
}

View file

@ -12,25 +12,56 @@
namespace Sylius\Behat\Context\Ui;
use Behat\Behat\Context\Context;
use Sylius\Behat\Page\Admin\Product\IndexPageInterface;
use Sylius\Behat\Page\Admin\Product\ShowPageInterface;
use Sylius\Behat\Page\Product\ProductShowPage;
use Sylius\Behat\Page\Admin\Product\ShowPage as AdminProductShowPage;
use Sylius\Behat\Page\Product\ProductShowPageInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Test\Services\SharedStorageInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
* @author Magdalena Banasiak <magdalena.banasiak@lakion.com>
*/
final class ProductContext implements Context
{
/**
* @var ProductShowPageInterface
* @var SharedStorageInterface
*/
private $sharedStorage;
/**
* @var ProductShowPage
*/
private $productShowPage;
/**
* @param ProductShowPageInterface $productShowPage
* @var AdminProductShowPage
*/
public function __construct(ProductShowPageInterface $productShowPage)
{
private $adminProductShowPage;
/**
* @var IndexPageInterface
*/
private $adminProductIndexPage;
/**
* @param SharedStorageInterface $sharedStorage
* @param ProductShowPageInterface $productShowPage
* @param ShowPageInterface $adminProductShowPage
* @param IndexPageInterface $adminProductIndexPage
*/
public function __construct(
SharedStorageInterface $sharedStorage,
ProductShowPageInterface $productShowPage,
ShowPageInterface $adminProductShowPage,
IndexPageInterface $adminProductIndexPage
) {
$this->sharedStorage = $sharedStorage;
$this->productShowPage = $productShowPage;
$this->adminProductShowPage = $adminProductShowPage;
$this->adminProductIndexPage = $adminProductIndexPage;
}
/**
@ -52,4 +83,26 @@ final class ProductContext implements Context
expect($this->productShowPage->isOpen(['product' => $product]))->toBe(false);
}
/**
* @When I delete the :product product
*/
public function iDeleteProduct(ProductInterface $product)
{
$this->adminProductShowPage->open(['id' => $product->getId()]);
$this->adminProductShowPage->deleteProduct();
$this->sharedStorage->set('product', $product);
}
/**
* @Then /^(this product) should not exist in the product catalog$/
*/
public function productShouldNotExist(ProductInterface $product)
{
$this->adminProductIndexPage->open();
expect($this->adminProductIndexPage->isThereProduct($product))->toBe(false);
}
}

View file

@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Behat\Page\Admin\Product;
use Behat\Mink\Session;
use Sylius\Behat\Page\SymfonyPage;
use Sylius\Behat\TableManipulatorInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Symfony\Component\Routing\RouterInterface;
/**
* @author Magdalena Banasiak <magdalena.banasiak@lakion.com>
*/
class IndexPage extends SymfonyPage implements IndexPageInterface
{
/**
* @var TableManipulatorInterface
*/
private $tableManipulator;
/**
* {@inheritdoc}
*
* @param TableManipulatorInterface $tableManipulator
*/
public function __construct(
Session $session,
array $parameters,
RouterInterface $router,
TableManipulatorInterface $tableManipulator
) {
parent::__construct($session, $parameters, $router);
$this->tableManipulator = $tableManipulator;
}
/**
* {@inheritdoc}
*/
public function isThereProduct(ProductInterface $product)
{
if (!$table = $this->getDocument()->find('css', 'table')) {
return false;
}
$row = $this->tableManipulator->getRowWithFields($table, ['id' => $product->getId()]);
return null === $row;
}
/**
* {@inheritdoc}
*/
protected function getRouteName()
{
return 'sylius_backend_product_index';
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Behat\Page\Admin\Product;
use Sylius\Behat\Page\PageInterface;
use Sylius\Component\Core\Model\ProductInterface;
/**
* @author Magdalena Banasiak <magdalena.banasiak@lakion.com>
*/
interface IndexPageInterface extends PageInterface
{
/**
* @param ProductInterface $product
*
* @return bool
*/
public function isThereProduct(ProductInterface $product);
}

View file

@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Behat\Page\Admin\Product;
use Sylius\Behat\Page\SymfonyPage;
/**
* @author Magdalena Banasiak <magdalena.banasiak@lakion.com>
*/
class ShowPage extends SymfonyPage implements ShowPageInterface
{
/**
* {@inheritdoc}
*/
public function deleteProduct()
{
$this->getDocument()->pressButton('Delete');
$confirmationModal = $this->getDocument()->find('css', '#confirmation-modal-confirm');
$this->waitForModalToAppear($confirmationModal);
$confirmationModal->find('css', 'a:contains("Delete")')->press();
}
/**
* {@inheritdoc}
*/
protected function getRouteName()
{
return 'sylius_backend_product_show';
}
}

View file

@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Behat\Page\Admin\Product;
use Sylius\Behat\Page\ElementNotFoundException;
use Sylius\Behat\Page\PageInterface;
/**
* @author Magdalena Banasiak <magdalena.banasiak@lakion.com>
*/
interface ShowPageInterface extends PageInterface
{
/**
* @throws ElementNotFoundException
*/
public function deleteProduct();
}

View file

@ -18,20 +18,29 @@ use PhpSpec\Exception\Example\NotEqualException;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Behat\Context\Domain\ProductContext;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\Repository\ProductVariantRepositoryInterface;
use Sylius\Component\Core\Test\Services\SharedStorageInterface;
use Sylius\Component\Product\Model\VariantInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Sylius\Component\Review\Model\ReviewInterface;
/**
* @mixin ProductContext
*
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
* @author Magdalena Banasiak <magdalena.banasiak@lakion.com>
*/
class ProductContextSpec extends ObjectBehavior
{
function let(SharedStorageInterface $sharedStorage, ProductVariantRepositoryInterface $productVariantRepository)
{
$this->beConstructedWith($sharedStorage, $productVariantRepository);
function let(
SharedStorageInterface $sharedStorage,
RepositoryInterface $productRepository,
ProductVariantRepositoryInterface $productVariantRepository,
RepositoryInterface $reviewRepository
) {
$this->beConstructedWith($sharedStorage, $productRepository, $productVariantRepository, $reviewRepository);
}
function it_is_initializable()
@ -53,7 +62,7 @@ class ProductContextSpec extends ObjectBehavior
$this->iDeleteTheVariantOfProduct($productVariant);
}
function it_tries_to_delete_a_product_variant_that_should_not_be_delted_from_the_repository(
function it_tries_to_delete_a_product_variant_that_should_not_be_deleted_from_the_repository(
SharedStorageInterface $sharedStorage,
ProductVariantRepositoryInterface $productVariantRepository,
ProductVariantInterface $productVariant
@ -66,8 +75,10 @@ class ProductContextSpec extends ObjectBehavior
function it_checks_if_a_product_variant_exists_in_the_repository(
SharedStorageInterface $sharedStorage,
ProductVariantRepositoryInterface $productVariantRepository
ProductVariantRepositoryInterface $productVariantRepository,
ProductVariantInterface $productVariant
) {
$productVariant->getId()->willReturn(1);
$sharedStorage->get('product_variant_id')->willReturn(1);
$productVariantRepository->find(1)->willReturn(null);
@ -79,19 +90,20 @@ class ProductContextSpec extends ObjectBehavior
ProductVariantRepositoryInterface $productVariantRepository,
ProductVariantInterface $productVariant
) {
$productVariant->getId()->willReturn(1);
$sharedStorage->get('product_variant_id')->willReturn(1);
$productVariantRepository->find(1)->willReturn($productVariant);
$this->shouldThrow(NotEqualException::class)->during('productVariantShouldNotExistInTheProductCatalog', [$productVariant]);
$this->shouldThrow(NotEqualException::class)->during('productVariantShouldNotExistInTheProductCatalog');
}
function it_checks_if_a_proper_exception_has_been_thrown_in_the_previous_step(
function it_checks_if_a_proper_exception_was_thrown_in_the_previous_step_of_variant_deletion(
SharedStorageInterface $sharedStorage,
DBALException $exception
) {
$sharedStorage->get('last_exception')->willReturn($exception);
$this->iShouldBeNotifiedThatThisVariantIsInUseAndCannotBeDeleted();
$this->iShouldBeNotifiedThatThisProductVariantIsInUseAndCannotBeDeleted();
}
function it_throws_an_exception_if_a_wrong_exception_was_passed(
@ -100,18 +112,17 @@ class ProductContextSpec extends ObjectBehavior
) {
$sharedStorage->get('last_exception')->willReturn($exception);
$this->shouldThrow(FailureException::class)->during('iShouldBeNotifiedThatThisVariantIsInUseAndCannotBeDeleted', []);
$this->shouldThrow(FailureException::class)->during('iShouldBeNotifiedThatThisProductVariantIsInUseAndCannotBeDeleted');
}
function it_throws_an_exception_if_no_exception_was_passed(
SharedStorageInterface $sharedStorage
) {
function it_throws_an_exception_if_no_exception_was_passed(SharedStorageInterface $sharedStorage)
{
$sharedStorage->get('last_exception')->willThrow(\InvalidArgumentException::class);
$this->shouldThrow(\InvalidArgumentException::class)->during('iShouldBeNotifiedThatThisVariantIsInUseAndCannotBeDeleted', []);
$this->shouldThrow(\InvalidArgumentException::class)->during('iShouldBeNotifiedThatThisProductVariantIsInUseAndCannotBeDeleted');
}
function it_checks_if_product_variant_still_exist_in_a_reposiotry(
function it_checks_if_product_variant_still_exist_in_a_repository(
ProductVariantRepositoryInterface $productVariantRepository,
ProductVariantInterface $productVariant
) {
@ -130,4 +141,80 @@ class ProductContextSpec extends ObjectBehavior
$this->shouldThrow(FailureException::class)->during('productVariantShouldExistInTheProductCatalog', [$productVariant]);
}
function it_deletes_a_product(
SharedStorageInterface $sharedStorage,
RepositoryInterface $productRepository,
ProductInterface $product
) {
$product->getId()->willReturn(1);
$productRepository->find(1)->willReturn($product);
$sharedStorage->set('deleted_product', $product)->shouldBeCalled();
$productRepository->remove($product)->shouldBeCalled();
$this->iDeleteTheProduct($product);
}
function it_tries_to_delete_a_product_that_does_not_exist(
SharedStorageInterface $sharedStorage,
RepositoryInterface $productRepository,
ProductInterface $product
) {
$product->getId()->willReturn(1);
$productRepository->find(1)->willReturn(null);
$sharedStorage->set('deleted_product', $product)->shouldBeCalled();
$productRepository->remove($product)->willThrow(DBALException::class);
$sharedStorage->set('last_exception', Argument::type(DBALException::class))->shouldBeCalled();
$this->iDeleteTheProduct($product);
}
function it_checks_if_there_are_no_reviews(
SharedStorageInterface $sharedStorage,
RepositoryInterface $reviewRepository,
ProductInterface $product
) {
$sharedStorage->get('deleted_product')->willReturn($product);
$reviewRepository->findBy(['reviewSubject' => $product])->willReturn([]);
$this->thereAreNoProductReviews($product);
}
function it_throws_an_exception_if_reviews_of_the_product_still_exist(
SharedStorageInterface $sharedStorage,
RepositoryInterface $reviewRepository,
ProductInterface $product,
ReviewInterface $review
) {
$sharedStorage->get('deleted_product')->willReturn($product);
$reviewRepository->findBy(['reviewSubject' => $product])->willReturn([$review]);
$this->shouldThrow(FailureException::class)->during('thereAreNoProductReviews', [$product]);
}
function it_checks_if_there_are_no_variants(
SharedStorageInterface $sharedStorage,
ProductVariantRepositoryInterface $productVariantRepository,
ProductInterface $product
) {
$sharedStorage->get('deleted_product')->willReturn($product);
$productVariantRepository->findBy(['object' => $product])->willReturn([]);
$this->thereAreNoVariants($product);
}
function it_throws_an_exception_if_variants_of_the_product_still_exist(
SharedStorageInterface $sharedStorage,
ProductVariantRepositoryInterface $productVariantRepository,
ProductInterface $product,
VariantInterface $variant
) {
$sharedStorage->get('deleted_product')->willReturn($product);
$productVariantRepository->findBy(['object' => $product])->willReturn([$variant]);
$this->shouldThrow(FailureException::class)->during('thereAreNoVariants', [$product]);
}
}

View file

@ -20,7 +20,6 @@ use Sylius\Component\Core\Repository\ProductRepositoryInterface;
use Sylius\Component\Core\Test\Services\SharedStorageInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
/**
* @mixin ProductContext

View file

@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Behat\Context\Setup;
use Behat\Behat\Context\Context;
use Doctrine\Common\Persistence\ObjectManager;
use PhpSpec\ObjectBehavior;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Test\Services\SharedStorageInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Sylius\Component\Review\Model\ReviewerInterface;
use Sylius\Component\Review\Model\ReviewInterface;
/**
* @author Magdalena Banasiak <magdalena.banasiak@lakion.com>
*/
class ProductReviewContextSpec extends ObjectBehavior
{
function let(
SharedStorageInterface $sharedStorage,
FactoryInterface $reviewFactory,
RepositoryInterface $reviewRepository
) {
$this->beConstructedWith($sharedStorage, $reviewFactory, $reviewRepository);
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Behat\Context\Setup\ProductReviewContext');
}
function it_implements_context_interface()
{
$this->shouldImplement(Context::class);
}
function it_creates_a_review_for_a_given_product(
SharedStorageInterface $sharedStorage,
FactoryInterface $reviewFactory,
RepositoryInterface $productReviewRepository,
ProductInterface $product,
ReviewInterface $review
) {
$sharedStorage->get('product')->willReturn($product);
$reviewFactory->createNew()->willReturn($review);
$review->setTitle('title')->shouldBeCalled();
$review->setRating(5)->shouldBeCalled();
$review->setReviewSubject($product)->shouldBeCalled();
$product->addReview($review)->shouldBeCalled();
$productReviewRepository->add($review);
$this->productHasAReview($product);
}
}

View file

@ -0,0 +1,130 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Behat\Context\Ui;
use Behat\Behat\Context\Context;
use PhpSpec\Exception\Example\NotEqualException;
use PhpSpec\ObjectBehavior;
use Sylius\Behat\Context\Ui\ProductContext;
use Sylius\Behat\Page\Admin\Product\IndexPageInterface;
use Sylius\Behat\Page\Admin\Product\ShowPageInterface;
use Sylius\Behat\Page\Product\ProductShowPage;
use Sylius\Behat\Page\Admin\Product\ShowPage as AdminProductShowPage;
use Sylius\Behat\Page\Product\ProductShowPageInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Test\Services\SharedStorage;
use Sylius\Component\Core\Test\Services\SharedStorageInterface;
/**
* @mixin ProductContext
*
* @author Magdalena Banasiak <magdalena.banasiak@lakion.com>
*/
class ProductContextSpec extends ObjectBehavior
{
public function let(
SharedStorageInterface $sharedStorage,
ProductShowPageInterface $productShowPage,
ShowPageInterface $adminProductShowPage,
IndexPageInterface $adminProductIndexPage
) {
$this->beConstructedWith($sharedStorage, $productShowPage, $adminProductShowPage, $adminProductIndexPage);
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Behat\Context\Ui\ProductContext');
}
function it_implements_context_interface()
{
$this->shouldImplement(Context::class);
}
function it_checks_if_i_am_able_to_access_product_page(ProductShowPageInterface $productShowPage, ProductInterface $product)
{
$productShowPage->tryToOpen(['product' => $product])->shouldBeCalled();
$productShowPage->isOpen(['product' => $product])->willReturn(true);
$this->iShouldBeAbleToAccessProduct($product);
}
function it_throws_an_exception_if_i_am_not_able_to_access_product_page_when_i_should(
ProductShowPageInterface $productShowPage,
ProductInterface $product
) {
$productShowPage->tryToOpen(['product' => $product])->shouldBeCalled();
$productShowPage->isOpen(['product' => $product])->willReturn(false);
$this->shouldThrow(NotEqualException::class)->during('iShouldBeAbleToAccessProduct',[$product]);
}
function it_checks_if_i_am_not_able_to_access_product_page(ProductShowPageInterface $productShowPage, ProductInterface $product)
{
$productShowPage->tryToOpen(['product' => $product])->shouldBeCalled();
$productShowPage->isOpen(['product' => $product])->willReturn(false);
$this->iShouldNotBeAbleToAccessProduct($product);
}
function it_throws_an_exception_if_i_am_able_to_access_product_page_when_i_should_not(
ProductShowPageInterface $productShowPage,
ProductInterface $product
) {
$productShowPage->tryToOpen(['product' => $product])->shouldBeCalled();
$productShowPage->isOpen(['product' => $product])->willReturn(true);
$this->shouldThrow(NotEqualException::class)->during('iShouldNotBeAbleToAccessProduct',[$product]);
}
function it_deletes_a_product(
ShowPageInterface $adminProductShowPage,
SharedStorageInterface $sharedStorage,
ProductInterface $product
) {
$sharedStorage->set('product', $product)->shouldBeCalled();
$product->getName()->willReturn('Model');
$product->getId()->willReturn(1);
$adminProductShowPage->open(['id' => 1])->shouldBeCalled();
$adminProductShowPage->deleteProduct()->shouldBeCalled();
$this->iDeleteProduct($product);
}
function it_checks_if_a_product_does_not_exist(
SharedStorageInterface $sharedStorage,
IndexPageInterface $adminProductIndexPage,
ProductInterface $product
) {
$sharedStorage->get('product')->willReturn($product);
$adminProductIndexPage->open()->shouldBeCalled();
$adminProductIndexPage->isThereProduct($product)->willReturn(false);
$this->productShouldNotExist($product);
}
function it_throws_an_exception_if_a_product_exists_when_it_should_not(
SharedStorageInterface $sharedStorage,
IndexPageInterface $adminProductIndexPage,
ProductInterface $product
) {
$sharedStorage->get('product')->willReturn($product);
$adminProductIndexPage->open()->shouldBeCalled();
$adminProductIndexPage->isThereProduct($product)->willReturn(true);
$this->shouldThrow(NotEqualException::class)->during('productShouldNotExist', [$product]);
}
}

View file

@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Behat\Page\Admin\Product;
use Behat\Mink\Session;
use PhpSpec\ObjectBehavior;
use Sylius\Behat\Page\Admin\Product\IndexPageInterface;
use Sylius\Behat\Page\Admin\Product\ProductShowPageInterface;
use Sylius\Behat\Page\SymfonyPage;
use Sylius\Behat\TableManipulatorInterface;
use Symfony\Component\Routing\RouterInterface;
/**
* @author Magdalena Banasiak <magdalena.banasiak@lakion.com>
*/
class IndexPageSpec extends ObjectBehavior
{
function let(Session $session, RouterInterface $router, TableManipulatorInterface $tableManipulator)
{
$this->beConstructedWith($session, [], $router, $tableManipulator);
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Behat\Page\Admin\Product\IndexPage');
}
function it_is_symfony_page()
{
$this->shouldHaveType(SymfonyPage::class);
}
function it_implements_product_index_page_interface()
{
$this->shouldImplement(IndexPageInterface::class);
}
}

View file

@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Behat\Page\Admin\Product;
use Behat\Mink\Session;
use PhpSpec\ObjectBehavior;
use Sylius\Behat\Page\Admin\Product\ShowPageInterface;
use Sylius\Behat\Page\SymfonyPage;
use Symfony\Component\Routing\RouterInterface;
/**
* @author Magdalena Banasiak <magdalena.banasiak@lakion.com>
*/
class ShowPageSpec extends ObjectBehavior
{
function let(Session $session, RouterInterface $router)
{
$this->beConstructedWith($session, [], $router);
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Behat\Page\Admin\Product\ShowPage');
}
function it_is_symfony_page()
{
$this->shouldHaveType(SymfonyPage::class);
}
function it_implements_product_show_page_interface()
{
$this->shouldImplement(ShowPageInterface::class);
}
}

View file

@ -1,58 +0,0 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\CoreBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Sylius\Component\Core\Model\ProductInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @author Mateusz Zalewski <mateusz.zalewski@lakion.com>
* @author Grzegorz Sadowski <grzegorz.sadowski@lakion.com>
*/
class ProductDeleteListener
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* @param LifecycleEventArgs $args
*/
public function postSoftDelete(LifecycleEventArgs $args)
{
if (!($product = $args->getEntity()) instanceof ProductInterface) {
return;
}
$reviewManager = $this->container->get('sylius.manager.product_review');
$reviewRepository = $this->container->get('sylius.repository.product_review');
$reviews = $reviewRepository->findBy(['reviewSubject' => $product]);
foreach ($reviews as $review) {
$reviewManager->remove($review);
}
$product->setAverageRating(0);
$reviewManager->flush();
}
}

View file

@ -61,7 +61,6 @@
<parameter key="sylius.listener.image_upload.class">Sylius\Bundle\CoreBundle\EventListener\ImageUploadListener</parameter>
<parameter key="sylius.listener.channel.class">Sylius\Bundle\CoreBundle\EventListener\ChannelDeletionListener</parameter>
<parameter key="sylius.listener.customer_aware.class">Sylius\Bundle\CoreBundle\EventListener\CustomerAwareListener</parameter>
<parameter key="sylius.listener.product_delete.class">Sylius\Bundle\CoreBundle\EventListener\ProductDeleteListener</parameter>
<parameter key="sylius.listener.customer_reviews_delete.class">Sylius\Bundle\CoreBundle\EventListener\CustomerReviewsDeleteListener</parameter>
<parameter key="sylius.active_promotions_by_channel_provider.class">Sylius\Component\Core\Provider\ActivePromotionsByChannelProvider</parameter>
@ -400,11 +399,6 @@
<tag name="kernel.event_listener" event="sylius.taxon.pre_update" method="uploadTaxonImage" />
</service>
<service id="sylius.listener.product_delete" class="%sylius.listener.product_delete.class%">
<argument type="service" id="service_container"/>
<tag name="doctrine.event_listener" event="postSoftDelete" method="postSoftDelete" />
</service>
<service id="sylius.image_uploader" class="%sylius.image_uploader.class%">
<argument type="service">
<service class="Gaufrette\Filesystem" factory-service="knp_gaufrette.filesystem_map" factory-method="get">

View file

@ -1,60 +0,0 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Bundle\CoreBundle\EventListener;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\Event\LifecycleEventArgs;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;
use Sylius\Component\Core\Model\Product;
use Sylius\Component\Review\Model\ReviewInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @author Mateusz Zalewski <mateusz.zalewski@lakion.com>
* @author Grzegorz Sadowski <grzegorz.sadowski@lakion.com>
*/
class ProductDeleteListenerSpec extends ObjectBehavior
{
function let(ContainerInterface $container)
{
$this->beConstructedWith($container);
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Bundle\CoreBundle\EventListener\ProductDeleteListener');
}
function it_removes_soft_deleted_product_reviews(
$container,
EntityRepository $reviewRepository,
ObjectManager $reviewManager,
LifecycleEventArgs $args,
Product $product,
ReviewInterface $review
) {
$args->getEntity()->willReturn($product)->shouldBeCalled();
$container->get('sylius.manager.product_review')->willReturn($reviewManager)->shouldBeCalled();
$container->get('sylius.repository.product_review')->willReturn($reviewRepository)->shouldBeCalled();
$reviewRepository->findBy(['reviewSubject' => $product])->willReturn([$review])->shouldBeCalled();
$reviewManager->remove($review)->shouldBeCalled();
$reviewManager->flush()->shouldBeCalled();
$product->setAverageRating(0)->shouldBeCalled();
$this->postSoftDelete($args);
}
}

View file

@ -73,10 +73,8 @@
<field name="updatedAt" column="updated_at" type="datetime" nullable="true">
<gedmo:timestampable on="update"/>
</field>
<field name="deletedAt" column="deleted_at" type="datetime" nullable="true" />
<field name="enabled" column="enabled" type="boolean" />
<gedmo:soft-deleteable field-name="deletedAt" />
<gedmo:loggable />
</mapped-superclass>

View file

@ -206,8 +206,8 @@ class OrmIndexer implements IndexerInterface
->delete(SearchIndex::class, 'u')
->where('u.itemId = :item_id')
->andWhere('u.entity = :entity_namespace')
->setParameter(':item_id', $entity->getId())
->setParameter(':entity_namespace', get_class($entity))
->setParameter(':item_id', $entity['id'])
->setParameter(':entity_namespace', get_class($entity['entity']))
;
$queryBuilder->getQuery()->getResult();

View file

@ -96,7 +96,7 @@ class OrmListener implements EventSubscriber
{
$entity = $args->getEntity();
if ($this->ormIndexer->isObjectIndexable($entity)) {
$this->scheduledForDeletion[] = $entity;
$this->scheduledForDeletion[] = ['id' => $entity->getId(), 'entity' => $entity];
}
}

View file

@ -76,7 +76,6 @@
<a href="{{ path('sylius_backend_product_show', {'id': product.id}) }}">
<h4 class="name">
{{ product.name }}
{% if product.deleted %}<span class="label label-danger">{{ 'sylius.deleted'|trans }}</span>{% endif %}
</h4>
</a>
{% if product.hasOptions %}

View file

@ -88,7 +88,6 @@
<a href="{{ path('sylius_backend_product_show', {'id': product.id}) }}">
<h4 class="name">
{{ product.name }}
{% if product.deleted %}<span class="label label-danger">{{ 'sylius.deleted'|trans }}</span>{% endif %}
</h4>
</a>
{% if product.hasOptions %}

View file

@ -17,14 +17,12 @@
<div class="page-header">
<div class="actions-menu">
{{ buttons.manage(path('sylius_backend_product_index'), 'sylius.product.manage'|trans) }}
{% if not product.deleted %}
{{ buttons.edit(path('sylius_backend_product_update', {'id': product.id})) }}
{{ buttons.delete(path('sylius_backend_product_delete', {'id': product.id}), null, false, false) }}
<a href="{{ path(product) }}" class="btn btn-info">
<i class="glyphicon glyphicon-shopping-cart"></i>
{{ 'sylius.product.show_in_store'|trans }}
</a>
{% endif %}
</div>
<h1><i class="glyphicon glyphicon-calendar"></i> {{ 'sylius.product.show_header'|trans({'%product%': product.name})|raw }}</h1>
</div>

View file

@ -20,11 +20,6 @@
</a>
{{ buttons.manage(path('sylius_backend_product_option_index'), 'sylius.product_option.manage'|trans) }}
{{ buttons.manage(path('sylius_backend_product_attribute_index'), 'sylius.product_attribute.manage'|trans) }}
{% if app.request.query.get('deleted') %}
<a href="{{ path('sylius_backend_product_index', {'deleted': false}) }}" class="btn btn-danger"><i class="glyphicon glyphicon-trash"></i> {{ 'sylius.hide_deleted'|trans }}</a>
{% else %}
<a href="{{ path('sylius_backend_product_index', {'deleted': true}) }}" class="btn btn-danger"><i class="glyphicon glyphicon-trash"></i> {{ 'sylius.show_deleted'|trans }}</a>
{% endif %}
</div>
<h1><i class="glyphicon glyphicon-th-list"></i> {{ 'sylius.product.index_header'|trans|raw }}</h1>
</div>

View file

@ -96,12 +96,8 @@
<a href="{{ path('sylius_backend_metadata_container_customize', { 'id': product.metadataIdentifier }) }}" class="btn btn-default">
<i class="glyphicon glyphicon-file"></i><span>{{ 'sylius.metadata.product.single_customize'|trans }}</span>
</a>
{% if product.deleted %}
{{ buttons.patch(path('sylius_backend_product_delete_restore', {'id': product.id}), 'sylius.restore'|trans, null, 'btn btn-primary') }}
{% else %}
{{ buttons.edit(path('sylius_backend_product_update', {'id': product.id})) }}
{{ buttons.delete(path('sylius_backend_product_delete', {'id': product.id})) }}
{% endif %}
</td>
</tr>
{% endfor %}

View file

@ -22,19 +22,15 @@
{% else %}
{{ buttons.enable(path('sylius_backend_product_enable', {'id': product.id})) }}
{% endif %}
{% if not product.deleted %}
<a href="{{ path('sylius_backend_metadata_container_customize', { 'id': product.metadataIdentifier }) }}" class="btn btn-default">
<i class="glyphicon glyphicon-file"></i><span>{{ 'sylius.metadata.product.single_customize'|trans }}</span>
</a>
{{ buttons.edit(path('sylius_backend_product_update', {'id': product.id})) }}
{{ buttons.delete(path('sylius_backend_product_delete', {'id': product.id}), null, false, true) }}
<a href="{{ path(product) }}" class="btn btn-info">
<i class="glyphicon glyphicon-shopping-cart"></i>
{{ 'sylius.product.show_in_store'|trans }}
</a>
{% else %}
{{ buttons.patch(path('sylius_backend_product_delete_restore', {'id': product.id}), 'sylius.restore'|trans, null, 'btn btn-primary') }}
{% endif %}
<a href="{{ path('sylius_backend_metadata_container_customize', { 'id': product.metadataIdentifier }) }}" class="btn btn-default">
<i class="glyphicon glyphicon-file"></i><span>{{ 'sylius.metadata.product.single_customize'|trans }}</span>
</a>
{{ buttons.edit(path('sylius_backend_product_update', {'id': product.id})) }}
{{ buttons.delete(path('sylius_backend_product_delete', {'id': product.id}), null, false, true) }}
<a href="{{ path(product) }}" class="btn btn-info">
<i class="glyphicon glyphicon-shopping-cart"></i>
{{ 'sylius.product.show_in_store'|trans }}
</a>
<a href="{{ path('sylius_backend_product_history', {'id': product.id}) }}" class="btn btn-info">
<i class="glyphicon glyphicon-calendar"></i>
{{ 'sylius.history.header'|trans }}
@ -42,7 +38,6 @@
</div>
<h1>
<i class="glyphicon glyphicon-info-sign"></i> {{ 'sylius.product.show_header'|trans({'%product%': product.name})|raw }}
{% if product.deleted %}<span class="label label-danger">{{ 'sylius.deleted'|trans }}</span>{% endif %}
</h1>
</div>
@ -277,7 +272,6 @@
<hr>
{% if not product.deleted %}
<div class="well well-sm">
{{ buttons.create(path('sylius_backend_product_variant_create', {'productId': product.id}), 'sylius.variant.create'|trans) }}
{% if product.hasOptions %}
@ -287,7 +281,6 @@
<strong>{{ 'sylius.product.variant_selection_method'|trans }}</strong>: {{ product.variantSelectionMethodLabel }}
</div>
</div>
{% endif %}
{{ list_variants(product.variants) }}

View file

@ -15,7 +15,6 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Sylius\Component\Archetype\Model\ArchetypeInterface as BaseArchetypeInterface;
use Sylius\Component\Attribute\Model\AttributeValueInterface as BaseAttributeValueInterface;
use Sylius\Component\Resource\Model\SoftDeletableTrait;
use Sylius\Component\Resource\Model\TimestampableTrait;
use Sylius\Component\Resource\Model\ToggleableTrait;
use Sylius\Component\Resource\Model\TranslatableTrait;
@ -28,7 +27,7 @@ use Sylius\Component\Variation\Model\VariantInterface as BaseVariantInterface;
*/
class Product implements ProductInterface
{
use SoftDeletableTrait, TimestampableTrait, ToggleableTrait;
use TimestampableTrait, ToggleableTrait;
use TranslatableTrait {
__construct as private initializeTranslationsCollection;
}

View file

@ -26,7 +26,6 @@ use Sylius\Component\Resource\Model\TranslatableInterface;
interface ProductInterface extends
ArchetypeSubjectInterface,
SlugAwareInterface,
SoftDeletableInterface,
TimestampableInterface,
ToggleableInterface,
ProductTranslationInterface,

View file

@ -269,37 +269,6 @@ class ProductSpec extends ObjectBehavior
$this->getUpdatedAt()->shouldReturn($date);
}
function it_has_no_deletion_date_by_default()
{
$this->getDeletedAt()->shouldReturn(null);
}
function it_is_not_be_deleted_by_default()
{
$this->shouldNotBeDeleted();
}
function its_deletion_date_is_mutable()
{
$deletedAt = new \DateTime();
$this->setDeletedAt($deletedAt);
$this->getDeletedAt()->shouldReturn($deletedAt);
}
function it_is_deleted_only_if_deletion_date_is_in_past()
{
$deletedAt = new \DateTime('yesterday');
$this->setDeletedAt($deletedAt);
$this->shouldBeDeleted();
$deletedAt = new \DateTime('tomorrow');
$this->setDeletedAt($deletedAt);
$this->shouldNotBeDeleted();
}
function it_is_enabled_by_default()
{
$this->shouldBeEnabled();