Answers.Body: <p>There is a way to do this, obviously, it is worth noting that the code below is written to be easy to understand and could be improved. Also, it is worth noting that there are plenty of things to think about when implementing something like this.</p>
  
  <p>Take careful consideration about how this will affect all parts of the e-commerce experience, whether that is carts, orders, adding to cart in the front end and cart management in the control panel.</p>
  
  <p>The following code will remove a tax adjustment based on the SKU of the line item and the name of the tax adjustment, your criteria may obviously vary.</p>
  
  <pre class="lang-php prettyprint-override"><code>use craft\commerce\elements\Order;
  use craft\commerce\Plugin;
  use craft\events\ModelEvent;
  // ...
  
  Event::on(Order::class,
      Order::EVENT_AFTER_SAVE,
      function(ModelEvent $event) {
          $lineItems = $event-&gt;sender-&gt;getLineItems();
          $adjustments = $event-&gt;sender-&gt;getAdjustments();
  
          // No need to do anything if this is an order or there aren't any line items or adjustments
          if (empty($lineItems) || empty($adjustments) || $event-&gt;sender-&gt;isCompleted) {
              return;
          }
  
          // Figure out which line items we want to remove tax from
          $removeTaxFromLineItemIds = [];
          foreach ($lineItems as $lineItem) {
              if ($lineItem-&gt;getSku() == 'MY-EXAMPLE-SKU') {
                  $removeTaxFromLineItemIds[] = $lineItem-&gt;id;
              }
          }
  
          if (empty($removeTaxFromLineItemIds) ) {
              return;
          }
  
          // Loop through adjustments and remove the tax adjustments for the line items we determined above
          $taxNameToRemove = 'My Tax';
          foreach ($adjustments as $key =&gt; $adjustment) {
              if (in_array($adjustment-&gt;lineItemId, $removeTaxFromLineItemIds) &amp;&amp; $adjustment-&gt;type === Tax::ADJUSTMENT_TYPE &amp;&amp; $adjustment-&gt;name == $taxNameToRemove) {
                  unset($adjustments[$key]);
                  Plugin::getInstance()-&gt;getOrderAdjustments()-&gt;deleteOrderAdjustmentByAdjustmentId($adjustment-&gt;id);
              }
          }
  
          $event-&gt;sender-&gt;setAdjustments($adjustments);
      }
  );
  </code></pre>
  
  <p>This code can be used inside a custom plugin or module.</p>
  
  <p>Hope this helps.</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-06-12T17:22:56.107Z
Answers.Text: Thanks for the great example. I'm curious why this happens in `EVENT_AFTER_SAVE`; when this set the adjustments at the end `$event->sender->setAdjustments($adjustments);` will that not be saved though? It seems like it's being set but not saved.
  
  2nd question: will the line `Plugin::getInstance()->getOrderAdjustments()->deleteOrderAdjustmentByAdjustmentId($adjustment->id);` delete the adjuster permanently or just from the order in question?  It seems like it will remove it completely but I just want to remove it for the order being saved.
Answers.UserDisplayName: Ryan
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-06-14T06:25:30.817Z
Answers.Text: It is done in `EVENT_AFTER_SAVE` to make sure we have given the system the chance to do everything it needs to. We using the `setAdjustments` to make sure they are set on the in memory version of the cart/order so that if something else is using the order they have the correct snapshot. I am then not saving it again to avoid infinite loops. `deleteOrderAdjustmentByAdjustmentId` only deletes that specific adjustment for that specific order and not across the whole system as you mention in your solution.
Answers.UserDisplayName: nfourtythree
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-06-12T12:30:42.480Z
Answers.LastActivityDate: 2020-06-14T06:14:11.077Z
Answers.LastEditDate: 2020-06-14T06:14:11.077Z
Answers.UserDisplayName: nfourtythree
Answers.Body: <p>@nfourtythree's answer was correct, but just for a 2nd example, here's what I ended up going with:</p>
  <pre><code>use craft\commerce\Plugin;
  use craft\commerce\elements\Order;
  use craft\commerce\models\OrderAdjustment;
  use craft\events\ModelEvent;
  use yii\base\Event;
  
  Event::on(
      Order::class,
      Order::EVENT_AFTER_SAVE,
      function(ModelEvent $event) {
  
          /* @var Order $order */
          $order = $event-&gt;sender;
  
          // abort if this is a cart because changes to adjustments wouldn't be applied otherwise
          if (! $order-&gt;isCompleted) {
              return;
          }
  
          // get adjustments and abort if there aren't any
          $adjustments = $order-&gt;getAdjustments();
          if (empty($adjustments)) {
              return;
          }
          
          $changesWereMade = false;
  
          foreach ($adjustments as $key =&gt; $adjustment) {
              /* @var OrderAdjustment $adjustment */
              if ($someExampleCondition) {
                  Plugin::getInstance()-&gt;getOrderAdjustments()-&gt;deleteOrderAdjustmentByAdjustmentId($adjustment-&gt;id);
                  unset($adjustments[$key]);
                  $changesWereMade = true;
              }
          }
  
          if ($changesWereMade) {
              $order-&gt;setAdjustments($adjustments);
          }
  
      });
  </code></pre>
  <p><code>$someExampleCondition</code> should obviously be changed to your needs. My condition is to filter out taxes that weren't relevant to the country the order was being placed in, since Commerce doesn't yet offer a multi-store solution.</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-06-14T06:30:37.013Z
Answers.Text: **1) The method deleteOrderAdjustmentByAdjustmentId($adjustment->id) would delete a specific tax adjuster from the system completely when I just want it removed from the cart/order.**
  
  This is incorrect, it only deletes the specific adjustment for that specific cart/order not across the whole system
Answers.UserDisplayName: nfourtythree
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-06-14T06:32:47.733Z
Answers.Text: **Carts' adjustments are dynamically pulling in the applicable tax adjusters in the system at that time, whereas an order's adjustments are recorded, so you can actually delete those off the order. Therefore, the method setAdjustments($adjustments) does nothing to a cart's adjustments, but it does actually affect an order's.** This is also not quite true, when a cart is saved its adjustments are also saved in the DB in the `commerce_orderadjustments` table. The only real difference between a cart and and order is `isCompleted`
Answers.UserDisplayName: nfourtythree
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-06-14T06:38:32.957Z
Answers.Text: Your solution will only affect completed orders which isn't advisable. This would mean that someone could not specifically add the tax to a line item that meeting the criteria when editing an order in the control panel. Your solution doesn't solve the initial question as the tax adjuster isn't removed from the cart. Which means if the adjustment is adding a value (e.g. $5) the customer would still have to pay it and then you code would remove that value after that order is saved when it is completed. I would revisit my answer, please ping me on discord if you would like to discuss it
Answers.UserDisplayName: nfourtythree
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-06-21T16:56:07.840Z
Answers.Text: @nfourtythree's you're right, and thanks for the patience and help! Under a bit of a deadline so I jumped to conclusions :/
Answers.UserDisplayName: Ryan
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-06-13T16:57:09.373Z
Answers.LastActivityDate: 2020-06-21T16:55:03.287Z
Answers.LastEditDate: 2020-06-21T16:55:03.287Z
Answers.UserDisplayName: Ryan
Body: <p>I need to remove adjusters from each line item if a certain condition is not met. I see that in the CP you can manually remove them by editing the line item but hunting down this code in Commerce's codebase has proven difficult.
  <a href="https://i.sstatic.net/c1kSe.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/c1kSe.png" alt="remove adjuster"></a></p>
  
  <p>Here's some of my code. I'm intending to keep certain adjusters from appearing for all line items before they save. How can I do this?</p>
  
  <pre><code>use yii\base\Event;
  use craft\commerce\services\LineItems;
  use craft\commerce\events\LineItemEvent;
  use craft\commerce\models\OrderAdjustment;
  use craft\commerce\models\LineItem;
  
  Event::on(
      LineItems::class,
      LineItems::EVENT_BEFORE_SAVE_LINE_ITEM,
      function(LineItemEvent $event) {
  
          $lineItem = $event-&gt;lineItem;
          foreach ($lineItem-&gt;getAdjustments() as $adjustment) {
              /* @var OrderAdjustment $adjustment */
  
              if ($someExampleCondition) {
                  // remove adjustment
              }
  
          }
  
      });
  </code></pre>
  
  <p>But when I inspect an <code>OrderAdjustment</code> model or <code>LineItem</code> model, I don't see any methods about removing or setting adjusters, only getting them.</p>
  
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2020-06-10T19:54:09.960Z
Comments.Text: Did you already try modifying the array passed in `$event->types`? https://docs.craftcms.com/commerce/v3/adjusters.html#replace-an-adjuster
Comments.UserDisplayName: Matt Stein
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2020-06-10T20:11:30.483Z
Comments.Text: I did and when I dd `$event->types` it's not very helpful: 
  `[
      0 => 'craft\\commerce\\adjusters\\Shipping'
      1 => 'craft\\commerce\\adjusters\\Discount'
      2 => 'craft\\commerce\\adjusters\\Tax'
  ]`
  I need to loop through all the Tax adjusters and remove the ones that don't apply.
Comments.UserDisplayName: Ryan
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2020-06-10T20:20:32.333Z
Comments.Text: It’d probably be worth using Xdebug to see what’s in each one, but why not isolate the tax adjuster(s) with `if ($type instanceof craft\commerce\adjusters\Tax) {` and then use your own logic to drop it from the set when it doesn’t apply? Basically the inverse of the example I linked above.
Comments.UserDisplayName: Matt Stein
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2020-06-10T20:30:57.807Z
Comments.Text: Err ... because you’re working with the line item and not the order. Which is not the same thing, sorry!
Comments.UserDisplayName: Matt Stein
Tags: <plugin-development><plugin-craftcommerce><events><adjusters><tax>
Title: Commerce: Remove tax adjuster from line item in plugin
ThreadText: # Commerce: Remove tax adjuster from line item in plugin
  
  (Asked by: username_0 on 2020-06-09)
  
  I need to remove adjusters from each line item if a certain condition is not met. I see that in the CP you can manually remove them by editing the line item but hunting down this code in Commerce's codebase has proven difficult.
  [![remove adjuster](https://i.sstatic.net/c1kSe.png)](https://i.sstatic.net/c1kSe.png)
  
  Here's some of my code. I'm intending to keep certain adjusters from appearing for all line items before they save. How can I do this?
  
  ```
  use yii\base\Event;
  use craft\commerce\services\LineItems;
  use craft\commerce\events\LineItemEvent;
  use craft\commerce\models\OrderAdjustment;
  use craft\commerce\models\LineItem;
  
  Event::on(
      LineItems::class,
      LineItems::EVENT_BEFORE_SAVE_LINE_ITEM,
      function(LineItemEvent $event) {
  
          $lineItem = $event->lineItem;
          foreach ($lineItem->getAdjustments() as $adjustment) {
              /* @var OrderAdjustment $adjustment */
  
              if ($someExampleCondition) {
                  // remove adjustment
              }
  
          }
  
      });
  
  ```
  
  But when I inspect an `OrderAdjustment` model or `LineItem` model, I don't see any methods about removing or setting adjusters, only getting them.
  
  **Tags:** plugin-development, plugin-craftcommerce, events, adjusters, tax
  
  > **Comments:**
  > **username_1**: Did you already try modifying the array passed in `$event->types`? https://docs.craftcms.com/commerce/v3/adjusters.html#replace-an-adjuster  
  > **username_0**: I did and when I dd `$event->types` it's not very helpful:  `[     0 => 'craft\\commerce\\adjusters\\Shipping'     1 => 'craft\\commerce\\adjusters\\Discount'     2 => 'craft\\commerce\\adjusters\\Tax' ]` I need to loop through all the Tax adjusters and remove the ones that don't apply.  
  > **username_1**: It’d probably be worth using Xdebug to see what’s in each one, but why not isolate the tax adjuster(s) with `if ($type instanceof craft\commerce\adjusters\Tax) {` and then use your own logic to drop it from the set when it doesn’t apply? Basically the inverse of the example I linked above.  
  > **username_1**: Err ... because you’re working with the line item and not the order. Which is not the same thing, sorry!  
  
  ---
  
  ## 2 answers
  
  ### Answer by username_2 on 2020-06-12. Score: 3 | Accepted answer
  
  There is a way to do this, obviously, it is worth noting that the code below is written to be easy to understand and could be improved. Also, it is worth noting that there are plenty of things to think about when implementing something like this.
  
  Take careful consideration about how this will affect all parts of the e-commerce experience, whether that is carts, orders, adding to cart in the front end and cart management in the control panel.
  
  The following code will remove a tax adjustment based on the SKU of the line item and the name of the tax adjustment, your criteria may obviously vary.
  
  ```php
  use craft\commerce\elements\Order;
  use craft\commerce\Plugin;
  use craft\events\ModelEvent;
  // ...
  
  Event::on(Order::class,
      Order::EVENT_AFTER_SAVE,
      function(ModelEvent $event) {
          $lineItems = $event->sender->getLineItems();
          $adjustments = $event->sender->getAdjustments();
  
          // No need to do anything if this is an order or there aren't any line items or adjustments
          if (empty($lineItems) || empty($adjustments) || $event->sender->isCompleted) {
              return;
          }
  
          // Figure out which line items we want to remove tax from
          $removeTaxFromLineItemIds = [];
          foreach ($lineItems as $lineItem) {
              if ($lineItem->getSku() == 'MY-EXAMPLE-SKU') {
                  $removeTaxFromLineItemIds[] = $lineItem->id;
              }
          }
  
          if (empty($removeTaxFromLineItemIds) ) {
              return;
          }
  
          // Loop through adjustments and remove the tax adjustments for the line items we determined above
          $taxNameToRemove = 'My Tax';
          foreach ($adjustments as $key => $adjustment) {
              if (in_array($adjustment->lineItemId, $removeTaxFromLineItemIds) && $adjustment->type === Tax::ADJUSTMENT_TYPE && $adjustment->name == $taxNameToRemove) {
                  unset($adjustments[$key]);
                  Plugin::getInstance()->getOrderAdjustments()->deleteOrderAdjustmentByAdjustmentId($adjustment->id);
              }
          }
  
          $event->sender->setAdjustments($adjustments);
      }
  );
  
  ```
  
  This code can be used inside a custom plugin or module.
  
  Hope this helps.
  
  > **Comments:**
  > **username_0**: Thanks for the great example. I'm curious why this happens in `EVENT_AFTER_SAVE`; when this set the adjustments at the end `$event->sender->setAdjustments($adjustments);` will that not be saved though? It seems like it's being set but not saved.  2nd question: will the line `Plugin::getInstance()->getOrderAdjustments()->deleteOrderAdjustmentByAdjustmentId($adjustment->id);` delete the adjuster permanently or just from the order in question?  It seems like it will remove it completely but I just want to remove it for the order being saved.  
  > **username_2**: It is done in `EVENT_AFTER_SAVE` to make sure we have given the system the chance to do everything it needs to. We using the `setAdjustments` to make sure they are set on the in memory version of the cart/order so that if something else is using the order they have the correct snapshot. I am then not saving it again to avoid infinite loops. `deleteOrderAdjustmentByAdjustmentId` only deletes that specific adjustment for that specific order and not across the whole system as you mention in your solution.  
  
  ---
  
  ### Answer by username_0 on 2020-06-13. Score: 2
  
  @username_2's answer was correct, but just for a 2nd example, here's what I ended up going with:
  
  ```
  use craft\commerce\Plugin;
  use craft\commerce\elements\Order;
  use craft\commerce\models\OrderAdjustment;
  use craft\events\ModelEvent;
  use yii\base\Event;
  
  Event::on(
      Order::class,
      Order::EVENT_AFTER_SAVE,
      function(ModelEvent $event) {
  
          /* @var Order $order */
          $order = $event->sender;
  
          // abort if this is a cart because changes to adjustments wouldn't be applied otherwise
          if (! $order->isCompleted) {
              return;
          }
  
          // get adjustments and abort if there aren't any
          $adjustments = $order->getAdjustments();
          if (empty($adjustments)) {
              return;
          }
          
          $changesWereMade = false;
  
          foreach ($adjustments as $key => $adjustment) {
              /* @var OrderAdjustment $adjustment */
              if ($someExampleCondition) {
                  Plugin::getInstance()->getOrderAdjustments()->deleteOrderAdjustmentByAdjustmentId($adjustment->id);
                  unset($adjustments[$key]);
                  $changesWereMade = true;
              }
          }
  
          if ($changesWereMade) {
              $order->setAdjustments($adjustments);
          }
  
      });
  
  ```
  
  `$someExampleCondition` should obviously be changed to your needs. My condition is to filter out taxes that weren't relevant to the country the order was being placed in, since Commerce doesn't yet offer a multi-store solution.
  
  > **Comments:**
  > **username_2**: **1) The method deleteOrderAdjustmentByAdjustmentId($adjustment->id) would delete a specific tax adjuster from the system completely when I just want it removed from the cart/order.**  This is incorrect, it only deletes the specific adjustment for that specific cart/order not across the whole system  
  > **username_2**: **Carts' adjustments are dynamically pulling in the applicable tax adjusters in the system at that time, whereas an order's adjustments are recorded, so you can actually delete those off the order. Therefore, the method setAdjustments($adjustments) does nothing to a cart's adjustments, but it does actually affect an order's.** This is also not quite true, when a cart is saved its adjustments are also saved in the DB in the `commerce_orderadjustments` table. The only real difference between a cart and and order is `isCompleted`  
  > **username_2**: Your solution will only affect completed orders which isn't advisable. This would mean that someone could not specifically add the tax to a line item that meeting the criteria when editing an order in the control panel. Your solution doesn't solve the initial question as the tax adjuster isn't removed from the cart. Which means if the adjustment is adding a value (e.g. $5) the customer would still have to pay it and then you code would remove that value after that order is saved when it is completed. I would revisit my answer, please ping me on discord if you would like to discuss it  
  > **username_0**: @username_2's you're right, and thanks for the patience and help! Under a bit of a deadline so I jumped to conclusions :/  
  
  ---
  
  