diff --git a/modules/order/src/Plugin/Field/FieldWidget/UnitPriceWidget.php b/modules/order/src/Plugin/Field/FieldWidget/UnitPriceWidget.php index 4adbbf9581c9f730b5bc5775feef59f87c5f8495..1e59d70e8f1c678bc396ebd48f5250ae1785b25d 100644 --- a/modules/order/src/Plugin/Field/FieldWidget/UnitPriceWidget.php +++ b/modules/order/src/Plugin/Field/FieldWidget/UnitPriceWidget.php @@ -2,12 +2,19 @@ namespace Drupal\commerce_order\Plugin\Field\FieldWidget; +use Drupal\commerce\Context; +use Drupal\commerce_order\Entity\OrderInterface; +use Drupal\commerce_order\Entity\OrderItemInterface; +use Drupal\commerce_order\Form\OrderForm; use Drupal\commerce_price\Price; +use Drupal\commerce_price\Resolver\ChainPriceResolverInterface; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Plugin implementation of the 'commerce_unit_price' widget. @@ -20,7 +27,50 @@ use Drupal\Core\Form\FormStateInterface; * } * ) */ -class UnitPriceWidget extends WidgetBase { +class UnitPriceWidget extends WidgetBase implements ContainerFactoryPluginInterface { + + /** + * The chain price resolver. + * + * @var \Drupal\commerce_price\Resolver\ChainPriceResolverInterface + */ + protected $chainPriceResolver; + + /** + * Constructs a new UnitPriceWidget object. + * + * @param string $plugin_id + * The plugin_id for the widget. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The definition of the field to which the widget is associated. + * @param array $settings + * The widget settings. + * @param array $third_party_settings + * Any third party settings. + * @param \Drupal\commerce_price\Resolver\ChainPriceResolverInterface $price_resolver + * The chain price resolver. + */ + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ChainPriceResolverInterface $price_resolver) { + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); + + $this->chainPriceResolver = $price_resolver; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['third_party_settings'], + $container->get('commerce_price.chain_price_resolver') + ); + } /** * {@inheritdoc} @@ -65,7 +115,6 @@ class UnitPriceWidget extends WidgetBase { * {@inheritdoc} */ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { - $element = []; /** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */ $order_item = $items[$delta]->getEntity(); if ($this->getSetting('require_confirmation')) { @@ -79,7 +128,6 @@ class UnitPriceWidget extends WidgetBase { $element['amount'] = [ '#type' => 'commerce_price', '#title' => $this->fieldDefinition->getLabel(), - '#required' => $this->fieldDefinition->isRequired(), '#available_currencies' => array_filter($this->getFieldSetting('available_currencies')), ]; if (!$items[$delta]->isEmpty()) { @@ -107,21 +155,44 @@ class UnitPriceWidget extends WidgetBase { $field_name = $this->fieldDefinition->getName(); $path = array_merge($form['#parents'], [$field_name, 0]); $values = NestedArray::getValue($form_state->getValues(), $path); - if ($values) { - /** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */ - $order_item = $items[0]->getEntity(); - if (!$this->getSetting('require_confirmation') || !empty($values['override'])) { + $order_item = $items->getEntity(); + assert($order_item instanceof OrderItemInterface); + $unit_price = NULL; + + if (!empty($values['override']) || !$this->getSetting('require_confirmation')) { + // Verify the passed number was numeric before trying to set it. + try { $unit_price = Price::fromArray($values['amount']); $order_item->setUnitPrice($unit_price, TRUE); } - - // Put delta mapping in $form_state, so that flagErrors() can use it. - $field_state = static::getWidgetState($form['#parents'], $field_name, $form_state); - foreach ($items as $delta => $item) { - $field_state['original_deltas'][$delta] = $delta; + catch (\InvalidArgumentException $e) { + $form_state->setErrorByName(implode('][', $path), $this->t('%title must be a number.', [ + '%title' => $this->fieldDefinition->getLabel(), + ])); + } + } + // If this is a new order item, resolve a default unit price. + elseif ($order_item->isNew()) { + $purchased_entity = $order_item->getPurchasedEntity(); + if ($purchased_entity !== NULL) { + $order = $order_item->getOrder(); + if ($order === NULL) { + $form_object = $form_state->getFormObject(); + assert($form_object instanceof OrderForm); + $order = $form_object->getEntity(); + assert($order instanceof OrderInterface); + } + $context = new Context($order->getCustomer(), $order->getStore()); + $unit_price = $this->chainPriceResolver->resolve($purchased_entity, $order_item->getQuantity(), $context); + $order_item->setUnitPrice($unit_price, FALSE); } - static::setWidgetState($form['#parents'], $field_name, $form_state, $field_state); } + // Put delta mapping in $form_state, so that flagErrors() can use it. + $field_state = static::getWidgetState($form['#parents'], $field_name, $form_state); + foreach ($items as $delta => $item) { + $field_state['original_deltas'][$delta] = $delta; + } + static::setWidgetState($form['#parents'], $field_name, $form_state, $field_state); } /** @@ -130,7 +201,7 @@ class UnitPriceWidget extends WidgetBase { public static function isApplicable(FieldDefinitionInterface $field_definition) { $entity_type = $field_definition->getTargetEntityTypeId(); $field_name = $field_definition->getName(); - return $entity_type == 'commerce_order_item' && $field_name == 'unit_price'; + return $entity_type === 'commerce_order_item' && $field_name === 'unit_price'; } } diff --git a/modules/order/tests/src/FunctionalJavascript/OrderAdminTest.php b/modules/order/tests/src/FunctionalJavascript/OrderAdminTest.php index 25f97d18870e2f5c107e70c4a383420e9398a43b..46948f8e27818a6d25f274d1bcc04430e74727b0 100644 --- a/modules/order/tests/src/FunctionalJavascript/OrderAdminTest.php +++ b/modules/order/tests/src/FunctionalJavascript/OrderAdminTest.php @@ -40,6 +40,13 @@ class OrderAdminTest extends OrderWebDriverTestBase { */ protected $defaultProfile; + /** + * The second variation. + * + * @var \Drupal\commerce_product\Entity\ProductVariationInterface + */ + protected $secondVariation; + /** * {@inheritdoc} */ @@ -55,6 +62,19 @@ class OrderAdminTest extends OrderWebDriverTestBase { 'address' => $this->defaultAddress, ]); $this->defaultProfile->save(); + + // Create a product variation. + $this->secondVariation = $this->createEntity('commerce_product_variation', [ + 'type' => 'default', + 'sku' => $this->randomMachineName(), + 'price' => [ + 'number' => 5.55, + 'currency_code' => 'USD', + ], + ]); + $product = $this->variation->getProduct(); + $product->addVariation($this->secondVariation); + $product->save(); } /** @@ -76,17 +96,33 @@ class OrderAdminTest extends OrderWebDriverTestBase { // outcome. $this->assertSame([], \Drupal::state()->get("commerce_order_test_field_widget_form_alter")); - // Test creating an order item. - $entity = $this->variation->getSku() . ' (' . $this->variation->id() . ')'; + // Test creating order items. $page = $this->getSession()->getPage(); + + // First item with overriding the price. + $entity1 = $this->variation->getSku() . ' (' . $this->variation->id() . ')'; $page->checkField('Override the unit price'); - $page->fillField('order_items[form][inline_entity_form][purchased_entity][0][target_id]', $entity); + $page->fillField('order_items[form][inline_entity_form][purchased_entity][0][target_id]', $entity1); $page->fillField('order_items[form][inline_entity_form][quantity][0][value]', '1'); + $this->getSession()->getPage()->pressButton('Create order item'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertSession()->pageTextContainsOnce('Unit price must be a number.'); $page->fillField('order_items[form][inline_entity_form][unit_price][0][amount][number]', '9.99'); $this->getSession()->getPage()->pressButton('Create order item'); $this->assertSession()->assertWaitOnAjaxRequest(); $this->assertSession()->pageTextContains('9.99'); + // Second item without overriding the price. + $entity2 = $this->secondVariation->getSku() . ' (' . $this->secondVariation->id() . ')'; + $this->waitForAjaxToFinish(); + $this->getSession()->getPage()->pressButton('Add new order item'); + $this->waitForAjaxToFinish(); + $page->fillField('order_items[form][inline_entity_form][purchased_entity][0][target_id]', $entity2); + $page->fillField('order_items[form][inline_entity_form][quantity][0][value]', '1'); + $this->getSession()->getPage()->pressButton('Create order item'); + $this->waitForAjaxToFinish(); + $this->assertSession()->pageTextContains('5.55'); + // Test editing an order item. $edit_buttons = $this->xpath('//div[@data-drupal-selector="edit-order-items-wrapper"]//input[@value="Edit"]'); $edit_button = reset($edit_buttons); @@ -120,8 +156,8 @@ class OrderAdminTest extends OrderWebDriverTestBase { $this->assertEquals(1, count($order_number)); $order = Order::load(1); - $this->assertEquals(1, count($order->getItems())); - $this->assertEquals(new Price('5.33', 'USD'), $order->getTotalPrice()); + $this->assertEquals(2, count($order->getItems())); + $this->assertEquals(new Price('10.88', 'USD'), $order->getTotalPrice()); $this->assertCount(1, $order->getAdjustments()); $billing_profile = $order->getBillingProfile(); $this->assertEquals($this->defaultAddress, array_filter($billing_profile->get('address')->first()->toArray()));