From 732b72d64b989c88b120b826d81867038228b8e1 Mon Sep 17 00:00:00 2001
From: Matt Glaman <nmd.matt@gmail.com>
Date: Mon, 3 Jul 2017 10:48:02 -0500
Subject: [PATCH] Issue #2794909 by drugan, mglaman, Frank HH-germany,
 agoradesign, iampuma: Make the quantity field configurable to include HTML
 properties such as 'Step' property

---
 config/schema/commerce.schema.yml                  | 11 +++
 .../cart/src/Plugin/views/field/EditQuantity.php   | 15 +++-
 modules/order/src/Entity/OrderItem.php             |  1 +
 ...display.commerce_order_item.default.default.yml |  9 ++-
 .../Field/FieldWidget/CommerceNumberWidget.php     | 88 ++++++++++++++++++++++
 5 files changed, 120 insertions(+), 4 deletions(-)
 create mode 100644 src/Plugin/Field/FieldWidget/CommerceNumberWidget.php

diff --git a/config/schema/commerce.schema.yml b/config/schema/commerce.schema.yml
index bd79eed46..819fe02cf 100644
--- a/config/schema/commerce.schema.yml
+++ b/config/schema/commerce.schema.yml
@@ -92,3 +92,14 @@ views.filter.commerce_entity_bundle:
         hide_single_bundle:
           type: boolean
           label: 'Hide if there''s only one bundle.'
+
+field.widget.settings.commerce_number:
+  type: mapping
+  label: 'Number with step display format settings'
+  mapping:
+    placeholder:
+      type: label
+      label: 'Placeholder'
+    step:
+      type: string
+      label: 'Step'
diff --git a/modules/cart/src/Plugin/views/field/EditQuantity.php b/modules/cart/src/Plugin/views/field/EditQuantity.php
index 6bf1b7362..2365e61ad 100644
--- a/modules/cart/src/Plugin/views/field/EditQuantity.php
+++ b/modules/cart/src/Plugin/views/field/EditQuantity.php
@@ -88,18 +88,27 @@ public function viewsForm(array &$form, FormStateInterface $form_state) {
 
     $form[$this->options['id']]['#tree'] = TRUE;
     foreach ($this->view->result as $row_index => $row) {
+      /** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */
       $order_item = $this->getEntity($row);
+
+      /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
+      $form_display = commerce_get_entity_display('commerce_order_item', $order_item->bundle(), 'form');
+      $quantity_component = $form_display->getComponent('quantity');
+
       $quantity = $order_item->getQuantity();
+      if ($quantity_component['settings']['step'] == 1) {
+        $quantity = (int) $quantity;
+      }
 
       $form[$this->options['id']][$row_index] = [
         '#type' => 'number',
         '#title' => $this->t('Quantity'),
         '#title_display' => 'invisible',
-        '#default_value' => round($quantity),
+        '#default_value' => $quantity,
         '#size' => 4,
-        '#min' => 1,
+        '#min' => 0,
         '#max' => 9999,
-        '#step' => 1,
+        '#step' => $quantity_component['settings']['step'],
       ];
     }
     // Replace the form submit button label.
diff --git a/modules/order/src/Entity/OrderItem.php b/modules/order/src/Entity/OrderItem.php
index 0210f1ae9..21e6f34ce 100644
--- a/modules/order/src/Entity/OrderItem.php
+++ b/modules/order/src/Entity/OrderItem.php
@@ -261,6 +261,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setDescription(t('The number of purchased units.'))
       ->setReadOnly(TRUE)
       ->setSetting('unsigned', TRUE)
+      ->setSetting('min', 0)
       ->setDefaultValue(1)
       ->setDisplayOptions('form', [
         'type' => 'number',
diff --git a/modules/product/config/optional/core.entity_form_display.commerce_order_item.default.default.yml b/modules/product/config/optional/core.entity_form_display.commerce_order_item.default.default.yml
index 63c5c2a99..29ce0746b 100644
--- a/modules/product/config/optional/core.entity_form_display.commerce_order_item.default.default.yml
+++ b/modules/product/config/optional/core.entity_form_display.commerce_order_item.default.default.yml
@@ -6,6 +6,9 @@ dependencies:
   enforced:
     module:
       - commerce_product
+  module:
+    - commerce
+    - commerce_price
 id: commerce_order_item.default.default
 targetEntityType: commerce_order_item
 bundle: default
@@ -19,17 +22,21 @@ content:
       size: 60
       placeholder: ''
     third_party_settings: {  }
+    region: content
   quantity:
-    type: number
+    type: commerce_number
     weight: 1
     settings:
       placeholder: ''
+      step: '1'
     third_party_settings: {  }
+    region: content
   unit_price:
     type: commerce_price_default
     weight: 2
     settings: {  }
     third_party_settings: {  }
+    region: content
 hidden:
   created: true
   status: true
diff --git a/src/Plugin/Field/FieldWidget/CommerceNumberWidget.php b/src/Plugin/Field/FieldWidget/CommerceNumberWidget.php
new file mode 100644
index 000000000..5fa2759a6
--- /dev/null
+++ b/src/Plugin/Field/FieldWidget/CommerceNumberWidget.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Drupal\commerce\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Field\Plugin\Field\FieldWidget\NumberWidget;
+
+/**
+ * Plugin implementation of the 'commerce_number' widget.
+ *
+ * @FieldWidget(
+ *   id = "commerce_number",
+ *   label = @Translation("Commerce number field"),
+ *   field_types = {
+ *     "integer",
+ *     "decimal",
+ *     "float"
+ *   }
+ * )
+ */
+class CommerceNumberWidget extends NumberWidget {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return [
+      'placeholder' => '',
+      'step' => '1',
+    ] + parent::defaultSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    $element = parent::settingsForm($form, $form_state);
+    $field_settings = $this->getFieldSettings();
+
+    $step = '1';
+    switch ($this->fieldDefinition->getType()) {
+      case 'decimal':
+        $step = (string) pow(0.1, $field_settings['scale']);
+        break;
+
+      case 'float':
+        $step = 'any';
+        break;
+    }
+
+    $element['step'] = [
+      '#type' => 'number',
+      '#min' => 0,
+      '#step' => $step,
+      '#title' => t('Step'),
+      '#default_value' => $this->getSetting('step'),
+      '#description' => t('The minimum allowed amount to increment or decrement the field value with.'),
+      '#required' => TRUE,
+    ];
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $summary = parent::settingsSummary();
+    $summary[] = t('Step: @step', ['@step' => $this->getSetting('step')]);
+
+    return $summary;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+    $element = parent::formElement($items, $delta, $element, $form, $form_state);
+
+    if (!empty($this->getSetting('step'))) {
+      $element['value']['#step'] = $this->getSetting('step');
+    }
+
+    return $element;
+  }
+
+}
