From d152e39cd18d0d56ec6dac16daa0e84e46bffd51 Mon Sep 17 00:00:00 2001 From: Dmitriy Krushelnitskiy Date: Fri, 17 Feb 2017 16:05:22 +0200 Subject: [PATCH] CRM-7596: Import addresses from Magento Customer to Contact --- .../CopyCustomerAddressesToContactCommand.php | 84 ++++ .../Entity/Repository/CustomerRepository.php | 26 ++ .../Manager/CustomerAddressManager.php | 159 +++++++ .../Resources/config/services.yml | 7 + ...yCustomerAddressesToContactCommandTest.php | 140 ++++++ .../LoadMagentoChannel.php | 440 ++++++++++++++++++ .../Functional/Fixture/LoadCustomerData.php | 1 - 7 files changed, 856 insertions(+), 1 deletion(-) create mode 100644 src/Oro/Bundle/MagentoBundle/Command/CopyCustomerAddressesToContactCommand.php create mode 100644 src/Oro/Bundle/MagentoBundle/Manager/CustomerAddressManager.php create mode 100644 src/Oro/Bundle/MagentoBundle/Tests/Functional/Command/CopyCustomerAddressesToContactCommandTest.php create mode 100644 src/Oro/Bundle/MagentoBundle/Tests/Functional/Fixture/CopyCustomerAddressToContact/LoadMagentoChannel.php diff --git a/src/Oro/Bundle/MagentoBundle/Command/CopyCustomerAddressesToContactCommand.php b/src/Oro/Bundle/MagentoBundle/Command/CopyCustomerAddressesToContactCommand.php new file mode 100644 index 00000000000..c1a4d437d31 --- /dev/null +++ b/src/Oro/Bundle/MagentoBundle/Command/CopyCustomerAddressesToContactCommand.php @@ -0,0 +1,84 @@ +setName('oro:magento:copy-data-to-contact:addresses') + ->addOption( + 'id', + null, + InputOption::VALUE_OPTIONAL|InputOption::VALUE_IS_ARRAY, + 'If option exists then customer addresses will be copied to contact for given magento customer by id' + ) + ->addOption( + 'integration-id', + null, + InputOption::VALUE_OPTIONAL|InputOption::VALUE_IS_ARRAY, + 'If option exists then customer addresses will be copied to contact + for given magento customers by integration_id' + ) + ->addOption( + 'batch-size', + null, + InputOption::VALUE_OPTIONAL, + 'Number of customers in batch. The default value is 25.' + ) + ->setDescription('Make copy addresses of magento customers to the contact'); + } + + /** + * {@inheritdoc} + */ + public function execute(InputInterface $input, OutputInterface $output) + { + $logger = new OutputLogger($output); + $logger->info('Executing command started.'); + + $integrationIds = $input->getOption('integration-id'); + $ids = $input->getOption('id'); + $batchSize = $input->getOption('batch-size') ? $input->getOption('batch-size') : self::BATCH_SIZE; + + $logger->info('Parameters:'); + if ($integrationIds) { + foreach ($integrationIds as $item) { + $logger->info(sprintf('--integration-id=%s', $item)); + } + } + if ($ids) { + foreach ($integrationIds as $item) { + $logger->info(sprintf('--id=%s', $item)); + } + } + if ($batchSize) { + $logger->info(sprintf('--batch-size=%s', $batchSize)); + $logger->info(''); + } + + /** @var CustomerAddressManager $customerAddressManager */ + $customerAddressManager = $this->container->get('oro_magento.manager.customer_address_manager'); + $customerAddressManager->setLogger($logger); + $customerAddressManager->copyToContact($ids, $integrationIds, $batchSize); + $logger->info('Executing command finished.'); + } +} diff --git a/src/Oro/Bundle/MagentoBundle/Entity/Repository/CustomerRepository.php b/src/Oro/Bundle/MagentoBundle/Entity/Repository/CustomerRepository.php index d26d1fa5016..bfd7a0a8087 100644 --- a/src/Oro/Bundle/MagentoBundle/Entity/Repository/CustomerRepository.php +++ b/src/Oro/Bundle/MagentoBundle/Entity/Repository/CustomerRepository.php @@ -3,6 +3,8 @@ namespace Oro\Bundle\MagentoBundle\Entity\Repository; use Doctrine\ORM\QueryBuilder; + +use Oro\Bundle\BatchBundle\ORM\Query\BufferedQueryResultIterator; use Oro\Bundle\DashboardBundle\Helper\DateHelper; use Oro\Bundle\SecurityBundle\ORM\Walker\AclHelper; use Oro\Bundle\MagentoBundle\Entity\Customer; @@ -178,4 +180,28 @@ public function getReturningCustomersWhoMadeOrderQB() return $qb; } + + /** + * @param int[]|null $customerIds + * @param int[]|null $integrationIds + * + * @return BufferedQueryResultIterator + */ + public function getIteratorByIdsAndIntegrationIds($customerIds, $integrationIds) + { + $qb = $this->createQueryBuilder('c'); + $qb->orderBy('c.id'); + + if ($customerIds) { + $qb->andWhere('c.id in (:customerIds)') + ->setParameter('customerIds', $customerIds); + } + + if ($integrationIds) { + $qb->andWhere('c.channel in (:integrationIds)') + ->setParameter('integrationIds', $integrationIds); + } + + return new BufferedQueryResultIterator($qb->getQuery()); + } } diff --git a/src/Oro/Bundle/MagentoBundle/Manager/CustomerAddressManager.php b/src/Oro/Bundle/MagentoBundle/Manager/CustomerAddressManager.php new file mode 100644 index 00000000000..f4f00242fd7 --- /dev/null +++ b/src/Oro/Bundle/MagentoBundle/Manager/CustomerAddressManager.php @@ -0,0 +1,159 @@ +em = $em; + $this->accessor = PropertyAccess::createPropertyAccessor(); + } + + /** + * @param int[]|null $customersIds + * @param int[]|null $integrationIds + * @param int $batchSize + */ + public function copyToContact($customersIds = null, $integrationIds = null, $batchSize = 25) + { + $i = 0; + $this->logger->info(sprintf('Start process')); + $repository = $this->em->getRepository('OroMagentoBundle:Customer'); + + $iterator = $repository->getIteratorByIdsAndIntegrationIds($customersIds, $integrationIds); + $iterator->setBufferSize($batchSize); + $customerCount = $iterator->count(); + + $iterator->setPageCallback(function () use (&$i, $customerCount) { + $this->em->flush(); + $this->em->flush(); + $this->logger->info(sprintf('Processed %s customers from %s', $i, $customerCount)); + }); + + /** @var Customer $customer */ + foreach ($iterator as $customer) { + $i++; + $contact = $customer->getContact(); + if ($contact) { + $addresses = $customer->getAddresses(); + + if ($addresses->count() > 0) { + foreach ($addresses as $address) { + $newContactAddress = $this->convertToContactAddress($address); + if (!$this->contactHasAddress($contact, $newContactAddress)) { + $contact->addAddress($newContactAddress); + $message = 'Customer address with id=%s was copied in contact with id=%s'; + $this->logger->info(sprintf($message, $address->getId(), $contact->getId())); + } + } + $this->em->persist($contact); + } + } + } + + $this->em->flush(); + $this->logger->info(sprintf('Finish process')); + } + + /** + * @param Contact $contact + * @param ContactAddress $contactAddress + * + * @return bool + */ + protected function contactHasAddress(Contact $contact, ContactAddress $contactAddress) + { + $addresses = $contact->getAddresses(); + + foreach ($addresses as $address) { + if ($this->isEqualAddresses($address, $contactAddress)) { + return true; + } + } + + return false; + } + + /** + * @param ContactAddress $address1 + * @param ContactAddress $address2 + * + * @return bool + */ + protected function isEqualAddresses(ContactAddress $address1, ContactAddress $address2) + { + $countEqualProperty = 0; + foreach ($this->baseAddressProperties as $property) { + if ($this->accessor->getValue($address1, $property) === $this->accessor->getValue($address2, $property)) { + $countEqualProperty++; + } + } + + return $countEqualProperty === count($this->baseAddressProperties); + } + + /** + * @param Address $customerAddress + * + * @return ContactAddress + */ + protected function convertToContactAddress(Address $customerAddress) + { + $properties = $this->baseAddressProperties; + $properties[] = 'types'; + $properties[] = 'primary'; + + $contactAddress = new ContactAddress(); + foreach ($properties as $property) { + $this->accessor->setValue( + $contactAddress, + $property, + $this->accessor->getValue($customerAddress, $property) + ); + } + + return $contactAddress; + } +} diff --git a/src/Oro/Bundle/MagentoBundle/Resources/config/services.yml b/src/Oro/Bundle/MagentoBundle/Resources/config/services.yml index 160487fd2d4..b09a8643553 100644 --- a/src/Oro/Bundle/MagentoBundle/Resources/config/services.yml +++ b/src/Oro/Bundle/MagentoBundle/Resources/config/services.yml @@ -589,6 +589,13 @@ services: tags: - { name: oro_integration.delete_provider } + oro_magento.manager.customer_address_manager: + class: Oro\Bundle\MagentoBundle\Manager\CustomerAddressManager + arguments: + - "@doctrine.orm.entity_manager" + calls: + - ['setLogger', ["@logger"]] + oro_magento.importexport.address_import_helper: class: %oro_magento.importexport.address_import_helper.class% arguments: diff --git a/src/Oro/Bundle/MagentoBundle/Tests/Functional/Command/CopyCustomerAddressesToContactCommandTest.php b/src/Oro/Bundle/MagentoBundle/Tests/Functional/Command/CopyCustomerAddressesToContactCommandTest.php new file mode 100644 index 00000000000..256044139d8 --- /dev/null +++ b/src/Oro/Bundle/MagentoBundle/Tests/Functional/Command/CopyCustomerAddressesToContactCommandTest.php @@ -0,0 +1,140 @@ +initClient(); + $this->loadFixtures([LoadMagentoChannel::class]); + } + + public function testCommand() + { + $entityManager = $this->getContainer()->get('doctrine'); + $repo = $entityManager->getRepository('Oro\Bundle\MagentoBundle\Entity\Customer'); + $customers = $repo->findAll(); + /** @var Customer $customer */ + foreach ($customers as $customer) { + self::assertEquals(0, $customer->getContact()->getAddresses()->count()); + } + + $result = $this->runCommand('oro:magento:copy-data-to-contact:addresses', ['--batch-size=2']); + + $customers = $repo->findAll(); + /** @var Customer $customer */ + foreach ($customers as $customer) { + self::assertEquals(1, $customer->getContact()->getAddresses()->count()); + } + + $this->assertContains('Executing command started.', $result); + $this->assertContains('Executing command finished.', $result); + } + + public function testConvertAddressForCustomerById() + { + $testCustomer = $this->getReference('customer_1'); + $id = $testCustomer->getId(); + + $entityManager = $this->getContainer()->get('doctrine'); + $repo = $entityManager->getRepository('Oro\Bundle\MagentoBundle\Entity\Customer'); + /** @var Customer $customer */ + $customer = $repo->find($id); + self::assertEquals(0, $customer->getContact()->getAddresses()->count()); + + $result = $this->runCommand('oro:magento:copy-data-to-contact:addresses', ['--id=' . $id]); + + /** @var Customer $customer */ + $customer = $repo->find($id); + self::assertEquals(1, $customer->getContact()->getAddresses()->count()); + + $this->assertContains('Executing command started.', $result); + $this->assertContains('Executing command finished.', $result); + } + + public function testConvertAddressForCustomerByIds() + { + $testCustomerId1 = $this->getReference('customer_1')->getId(); + $testCustomerId2 = $this->getReference('customer_2')->getId(); + + $entityManager = $this->getContainer()->get('doctrine'); + $repo = $entityManager->getRepository('Oro\Bundle\MagentoBundle\Entity\Customer'); + $customers = $repo->findBy(['id' => [$testCustomerId1, $testCustomerId2]]); + /** @var Customer $customer */ + foreach ($customers as $customer) { + self::assertEquals(0, $customer->getContact()->getAddresses()->count()); + } + + $result = $this->runCommand('oro:magento:copy-data-to-contact:addresses', [ + '--id=' . $testCustomerId1, + '--id=' . $testCustomerId2 + ]); + + $customers = $repo->findBy(['id' => [$testCustomerId1, $testCustomerId2]]); + /** @var Customer $customer */ + foreach ($customers as $customer) { + self::assertEquals(1, $customer->getContact()->getAddresses()->count()); + } + + $this->assertContains('Executing command started.', $result); + $this->assertContains('Executing command finished.', $result); + } + + public function testConvertAddressForCustomerByIntegrationId() + { + $integrationId = $this->getReference('integration')->getId(); + $entityManager = $this->getContainer()->get('doctrine'); + $repo = $entityManager->getRepository('Oro\Bundle\MagentoBundle\Entity\Customer'); + $customers = $repo->findBy(['channel' => $integrationId]); + /** @var Customer $customer */ + foreach ($customers as $customer) { + self::assertEquals(0, $customer->getContact()->getAddresses()->count()); + } + + $result = $this->runCommand('oro:magento:copy-data-to-contact:addresses', [ + '--integration-id=' . $integrationId, + '--batch-size=2' + ]); + + $customers = $repo->findBy(['channel' => $integrationId]); + /** @var Customer $customer */ + foreach ($customers as $customer) { + self::assertEquals(1, $customer->getContact()->getAddresses()->count()); + } + + $this->assertContains('Executing command started.', $result); + $this->assertContains('Executing command finished.', $result); + } + + public function testConvertAddressForCustomerByIdAndAccountHasAddress() + { + $testCustomerId1 = $this->getReference('customer_1')->getId(); + + $entityManager = $this->getContainer()->get('doctrine'); + $repo = $entityManager->getRepository('Oro\Bundle\MagentoBundle\Entity\Customer'); + /** @var Customer $customer */ + $customer = $repo->find($testCustomerId1); + self::assertEquals(0, $customer->getContact()->getAddresses()->count()); + + for ($i = 0; $i < 2; $i++) { + $result = $this->runCommand('oro:magento:copy-data-to-contact:addresses', [ + '--id=' . $testCustomerId1 + ]); + } + + /** @var Customer $customer */ + $customer = $repo->find($testCustomerId1); + self::assertEquals(1, $customer->getContact()->getAddresses()->count()); + + $this->assertContains('Executing command started.', $result); + $this->assertContains('Executing command finished.', $result); + } +} diff --git a/src/Oro/Bundle/MagentoBundle/Tests/Functional/Fixture/CopyCustomerAddressToContact/LoadMagentoChannel.php b/src/Oro/Bundle/MagentoBundle/Tests/Functional/Fixture/CopyCustomerAddressToContact/LoadMagentoChannel.php new file mode 100644 index 00000000000..7918f84fb50 --- /dev/null +++ b/src/Oro/Bundle/MagentoBundle/Tests/Functional/Fixture/CopyCustomerAddressToContact/LoadMagentoChannel.php @@ -0,0 +1,440 @@ +factory = $container->get('oro_channel.builder.factory'); + } + + /** + * {@inheritDoc} + */ + public function load(ObjectManager $manager) + { + $this->em = $manager; + $this->countries = $this->loadStructure('OroAddressBundle:Country', 'getIso2Code'); + $this->regions = $this->loadStructure('OroAddressBundle:Region', 'getCombinedCode'); + $this->organization = $manager->getRepository('OroOrganizationBundle:Organization')->getFirst(); + + $this + ->createTransport() + ->createIntegration() + ->createChannel() + ->createWebSite() + ->createCustomerGroup() + ->createGuestCustomerGroup() + ->createStore(); + + $account = $this->createAccount(); + $this->setReference('account', $account); + for ($i = 1; $i <= 5; $i++) { + $magentoAddress = $this->createMagentoAddress($this->regions['US-AZ'], $this->countries['US']); + $customer = $this->createCustomer($i, $account, $magentoAddress); + $this->setReference('customer_' . $i, $customer); + } + + $this->setReference('integration', $this->integration); + + + + $this->em->flush(); + } + + /** + * @param $table + * @param $method + * + * @return array + */ + protected function loadStructure($table, $method) + { + $result = []; + $response = $this->em->getRepository($table)->findAll(); + foreach ($response as $row) { + $result[call_user_func([$row, $method])] = $row; + } + + return $result; + } + + /** + * @return $this + */ + protected function createIntegration() + { + $integration = new Integration(); + $integration->setName('Demo Web store'); + $integration->setType('magento'); + $integration->setConnectors(['customer', 'order', 'cart']); + $integration->setTransport($this->transport); + $integration->setOrganization($this->organization); + + $synchronizationSettings = ConfigObject::create(['isTwoWaySyncEnabled' => true]); + $integration->setSynchronizationSettings($synchronizationSettings); + + $this->em->persist($integration); + $this->integration = $integration; + + return $this; + } + + /** + * @return $this + */ + protected function createTransport() + { + $transport = new MagentoSoapTransport; + $transport->setAdminUrl('http://localhost/magento/admin'); + $transport->setApiKey('key'); + $transport->setApiUser('user'); + $transport->setIsExtensionInstalled(true); + $transport->setExtensionVersion(SoapTransport::REQUIRED_EXTENSION_VERSION); + $transport->setMagentoVersion('1.9.1.0'); + $transport->setIsWsiMode(false); + $transport->setWebsiteId('1'); + $transport->setWsdlUrl('http://localhost/magento/api/v2_soap?wsdl=1'); + $transport->setWebsites([['id' => 1, 'label' => 'Website ID: 1, Stores: English, French, German']]); + + $this->em->persist($transport); + $this->transport = $transport; + + return $this; + } + + /** + * @param $region + * @param $country + * + * @return MagentoAddress + */ + protected function createMagentoAddress($region, $country) + { + $address = new MagentoAddress; + $address->setRegion($region); + $address->setCountry($country); + $address->setCity('City'); + $address->setStreet('street'); + $address->setPostalCode(123456); + $address->setFirstName('John'); + $address->setLastName('Doe'); + $address->setLabel('label'); + $address->setPrimary(true); + $address->setOrganization('oro'); + $address->setOriginId(1); + $address->setChannel($this->integration); + $address->setOrganization($this->organization); + $address->setPrimary(true); + + $this->em->persist($address); + + return $address; + } + + /** + * @param $region + * @param $country + * + * @return Address + */ + protected function createAddress($region, $country) + { + $address = new Address; + $address->setRegion($region); + $address->setCountry($country); + $address->setCity('City'); + $address->setStreet('street'); + $address->setPostalCode(123456); + $address->setFirstName('John'); + $address->setLastName('Doe'); + $address->setOrganization($this->organization); + + $this->em->persist($address); + + return $address; + } + + /** + * @param $oid + * @param Account $account + * @param MagentoAddress $address + * + * @return Customer + */ + protected function createCustomer($oid, Account $account, MagentoAddress $address) + { + $customer = new Customer(); + $customer->setChannel($this->integration); + $customer->setDataChannel($this->channel); + $customer->setFirstName('John ' . $oid); + $customer->setLastName('Doe ' . $oid); + $customer->setEmail('test' . $oid . '@example.com'); + $customer->setOriginId($oid); + $customer->setIsActive(true); + $customer->setWebsite($this->website); + $customer->setStore($this->store); + $customer->setAccount($account); + $customer->setGender(Gender::MALE); + $customer->setGroup($this->customerGroup); + // TODO: DateTimeZones should be removed in BAP-8710. Tests should be passed for: + // - Oro\Bundle\MagentoBundle\Tests\Functional\Controller\Api\Rest\CustomerControllerTest + // - Oro\Bundle\MagentoBundle\Tests\Functional\Controller\Api\Rest\MagentoCustomerControllerTest + $customer->setCreatedAt(new \DateTime('now', new \DateTimezone('UTC'))); + $customer->setUpdatedAt(new \DateTime('now', new \DateTimezone('UTC'))); + $customer->addAddress($address); + $customer->setOwner($this->getUser()); + $customer->setOrganization($this->organization); + $customerAssociation = new CustomerAssociation(); + $customerAssociation->setTarget($account, $customer); + + $contact = $this->createContact($customer); + + $customer->setContact($contact); + + $this->em->persist($customer); + $this->em->persist($customerAssociation); + + return $customer; + } + + /** + * @param Customer $customer + * + * @return Contact + */ + protected function createContact(Customer $customer) + { + $contact = new Contact(); + $contact->setFirstName($customer->getFirstName()); + $contact->setLastName($customer->getLastName()); + $contact->setGender($customer->getGender()); + $contact->setOwner($this->getUser()); + + $contact->setOrganization($this->organization); + $this->em->persist($contact); + + return $contact; + } + + /** + * @return $this + */ + protected function createWebSite() + { + $website = new Website(); + $website->setName('web site'); + $website->setOriginId(1); + $website->setCode('web site code'); + $website->setChannel($this->integration); + + $this->setReference('website', $website); + $this->em->persist($website); + $this->website = $website; + + return $this; + } + + /** + * @return $this + */ + protected function createStore() + { + $store = new Store; + $store->setName('demo store'); + $store->setChannel($this->integration); + $store->setCode(1); + $store->setWebsite($this->website); + $store->setOriginId(1); + + $this->em->persist($store); + $this->store = $store; + $this->setReference('store', $store); + + return $this; + } + + /** + * @return Account + */ + protected function createAccount() + { + $account = new Account; + $account->setName('acc'); + $account->setOwner($this->getUser()); + $account->setOrganization($this->organization); + + $this->em->persist($account); + + return $account; + } + + /** + * @return $this + */ + protected function createCustomerGroup() + { + $customerGroup = new CustomerGroup; + $customerGroup->setName('group'); + $customerGroup->setChannel($this->integration); + $customerGroup->setOriginId(1); + + $this->em->persist($customerGroup); + $this->setReference('customer_group', $customerGroup); + $this->customerGroup = $customerGroup; + + return $this; + } + + /** + * @return $this + */ + protected function createGuestCustomerGroup() + { + $customerGroup = new CustomerGroup; + $customerGroup->setName('NOT LOGGED IN'); + $customerGroup->setChannel($this->integration); + $customerGroup->setOriginId(0); + + $this->em->persist($customerGroup); + return $this; + } + + /** + * @param Order $order + * + * @return OrderItem + */ + protected function createBaseOrderItem(Order $order) + { + $orderItem = new OrderItem(); + $orderItem->setId(mt_rand(0, 9999)); + $orderItem->setName('some order item'); + $orderItem->setSku('some sku'); + $orderItem->setQty(1); + $orderItem->setOrder($order); + $orderItem->setCost(51.00); + $orderItem->setPrice(75.00); + $orderItem->setWeight(6.12); + $orderItem->setTaxPercent(2); + $orderItem->setTaxAmount(1.5); + $orderItem->setDiscountPercent(4); + $orderItem->setDiscountAmount(0); + $orderItem->setRowTotal(234); + $orderItem->setOwner($this->organization); + + $this->em->persist($orderItem); + + return $orderItem; + } + + /** + * @return User + */ + protected function getUser() + { + if (!$this->user) { + $this->user = $this->em->getRepository('OroUserBundle:User')->findOneBy(['username' => 'admin']); + } + + return $this->user; + } + + /** + * @return LoadMagentoChannel + */ + protected function createChannel() + { + $channel = $this + ->factory + ->createBuilder() + ->setName(self::CHANNEL_NAME) + ->setChannelType(self::CHANNEL_TYPE) + ->setStatus(Channel::STATUS_ACTIVE) + ->setDataSource($this->integration) + ->setOwner($this->organization) + ->setEntities() + ->getChannel(); + + $this->em->persist($channel); + $this->em->flush(); + + $this->setReference('default_channel', $channel); + + $this->channel = $channel; + + return $this; + } +} diff --git a/src/Oro/Bundle/MagentoBundle/Tests/Functional/Fixture/LoadCustomerData.php b/src/Oro/Bundle/MagentoBundle/Tests/Functional/Fixture/LoadCustomerData.php index af530b11e56..7f96e9c25cd 100644 --- a/src/Oro/Bundle/MagentoBundle/Tests/Functional/Fixture/LoadCustomerData.php +++ b/src/Oro/Bundle/MagentoBundle/Tests/Functional/Fixture/LoadCustomerData.php @@ -13,7 +13,6 @@ use Oro\Bundle\UserBundle\Entity\User; use Oro\Bundle\UserBundle\Model\Gender; use Oro\Bundle\MagentoBundle\Entity\Customer; -use Oro\Bundle\MagentoBundle\Entity\Customer; class LoadCustomerData extends AbstractFixture implements ContainerAwareInterface, DependentFixtureInterface {