Adjustments widget does not include percentage, removes preset value when resaved

Created on 10 December 2024, 9 days ago

The default field widget for adjustments ignores the percentage value. There is no field for it and no value is saved for it. This overrides any previous percentage value when an order item is updated (when adjustment field widget is enabled on the order item type).

Not really sure how to reproduce on a fresh install.

1) Enable adjustments on an order item type form display.
2) Create an order with an order item that has at least one order item adjustment with the percentage value set to anything but NULL.
3) Edit the order and the order item.
4) Update the order item and save the order.
5) Check the order item adjustment. When resaved, the adjustment with the percentage value has gotten it's percentage value reset to NULL.

I have made a temporary fix for this with a custom field widget. But this behaviour seems wrong from what I can gather.

πŸ› Bug report
Status

Active

Version

2.36

Component

Order

Created by

πŸ‡ΈπŸ‡ͺSweden agogo

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @agogo
  • πŸ‡ΈπŸ‡ͺSweden agogo

    This is the custom field widget I made, if anyone else needs a temporary fix:

    <?php
    
    namespace Drupal\custom_module\Plugin\Field\FieldWidget;
    
    use Drupal\commerce_order\Adjustment;
    use Drupal\commerce_price\Price;
    use Drupal\Core\Field\FieldItemListInterface;
    use Drupal\Core\Field\WidgetBase;
    use Drupal\Core\Form\FormStateInterface;
    
    /**
     * Plugin implementation of 'commerce_adjustment_custom'.
     *
     * @FieldWidget(
     *   id = "commerce_adjustment_custom",
     *   label = @Translation("Adjustment (Custom)"),
     *   field_types = {
     *     "commerce_adjustment"
     *   }
     * )
     */
    class CustomAdjustmentWidget extends WidgetBase {
    
      /**
       * {@inheritdoc}
       */
      public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
        /** @var \Drupal\commerce_order\Adjustment $adjustment */
        $adjustment = $items[$delta]->value;
    
        $element['#type'] = 'container';
        $element['#attributes']['class'][] = 'form--inline';
        $element['#attached']['library'][] = 'commerce_price/admin';
    
        /** @var \Drupal\Component\Plugin\PluginManagerInterface $plugin_manager */
        $plugin_manager = \Drupal::service('plugin.manager.commerce_adjustment_type');
    
        $types = [
          '_none' => $this->t('- Select -'),
        ];
        foreach ($plugin_manager->getDefinitions() as $id => $definition) {
          if (!empty($definition['has_ui'])) {
            $types[$id] = $definition['label'];
          }
        }
    
        $element['type'] = [
          '#type' => 'select',
          '#title' => $this->t('Type'),
          '#options' => $types,
          '#weight' => 1,
          '#default_value' => ($adjustment) ? $adjustment->getType() : '_none',
        ];
    
        // If this is being added through the UI, the source ID should be empty,
        // and we will want to default it to custom.
        $source_id = ($adjustment) ? $adjustment->getSourceId() : NULL;
        $element['source_id'] = [
          '#type' => 'value',
          '#value' => empty($source_id) ? 'custom' : $source_id,
        ];
        // If this is being added through the UI, the adjustment should be locked.
        // UI added adjustments need to be locked to persist after an order refresh.
        $element['locked'] = [
          '#type' => 'value',
          '#value' => ($adjustment) ? $adjustment->isLocked() : TRUE,
        ];
    
        $element['percentage'] = [
          '#type' => 'value',
          '#value' => ($adjustment) ? $adjustment->getPercentage() : NULL,
        ];
    
        $states_selector_name = $this->fieldDefinition->getName() . "[$delta][type]";
        $element['definition'] = [
          '#type' => 'container',
          '#weight' => 2,
          '#states' => [
            'invisible' => [
              'select[name="' . $states_selector_name . '"]' => ['value' => '_none'],
            ],
          ],
        ];
        $element['definition']['label'] = [
          '#type' => 'textfield',
          '#title' => $this->t('Label'),
          '#size' => 20,
          '#default_value' => ($adjustment) ? $adjustment->getLabel() : '',
        ];
        $element['definition']['amount'] = [
          '#type' => 'commerce_price',
          '#title' => $this->t('Amount'),
          '#default_value' => ($adjustment) ? $adjustment->getAmount()->toArray() : NULL,
          '#allow_negative' => TRUE,
          '#states' => [
            'optional' => [
              'select[name="' . $states_selector_name . '"]' => ['value' => '_none'],
            ],
          ],
          '#attributes' => ['class' => ['clearfix']],
        ];
        $element['definition']['included'] = [
          '#type' => 'checkbox',
          '#title' => $this->t('Included in the base price'),
          '#default_value' => ($adjustment) ? $adjustment->isIncluded() : FALSE,
        ];
    
        return $element;
      }
    
      /**
       * {@inheritdoc}
       */
      public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
        foreach ($values as $key => $value) {
          if ($value['type'] == '_none') {
            continue;
          }
          // The method can be called with invalid or incomplete data, in
          // preparation for validation. Passing such data to the Adjustment
          // object would result in an exception.
          if (empty($value['definition']['label'])) {
            $form_state->setErrorByName('adjustments[' . $key . '][definition][label]', $this->t('The adjustment label field is required.'));
            continue;
          }
    
          $values[$key] = new Adjustment([
            'type' => $value['type'],
            'label' => $value['definition']['label'],
            'amount' => new Price($value['definition']['amount']['number'], $value['definition']['amount']['currency_code']),
            'percentage' => !empty($value['percentage']) ? $value['percentage'] : NULL,
            'source_id' => $value['source_id'],
            'included' => $value['definition']['included'],
            'locked' => $value['locked'],
          ]);
        }
        return $values;
      }
    
    }
    
    
Production build 0.71.5 2024