We need a Shipments tab on orders, just like there is one for Payments.

Things we should be able to do, in order of priority:
0) See the shipping profile and each shipment.
1) Modify the shipment profile
2) Perform state transitions on the created shipments.
3) Edit and delete existing shipments.
4) Add new shipments.

CommentFileSizeAuthor
#66 Example2a.jpg40.89 KBmelpers
#66 Example1e.jpg23 KBmelpers
#66 Example1d.jpg32.72 KBmelpers
#66 Example1c.jpg22.16 KBmelpers
#66 Example1b.jpg58.27 KBmelpers
#66 Example1a.jpg34.15 KBmelpers
#62 shipment-form.png303.16 KBbojanz
#59 2854035-59.patch71.05 KBalexpott
#59 57-59-interdiff.txt528 bytesalexpott
#57 2854035-57.patch70.58 KBalexpott
#57 54-57-interdiff.txt8.97 KBalexpott
#54 2854035-53.patch70.92 KBalexpott
#54 52-53-interdiff.txt375 bytesalexpott
#53 2854035-52.patch70.92 KBalexpott
#53 51-52-interdiff.txt553 bytesalexpott
#51 2854035-51.patch71.62 KBalexpott
#51 49-51-interdiff.txt2.71 KBalexpott
#49 2854035-49.patch69.31 KBalexpott
#49 48-49-interdiff.txt4.9 KBalexpott
#48 2854035-3-48.patch71.36 KBalexpott
#48 47-48-interdiff.txt1.63 KBalexpott
#47 commerce_shipping-create-admin-ui-for-shipments-2854035-47.patch72.29 KBjsacksick
#47 interdiff_45-47.txt16.05 KBjsacksick
#45 interdiff_42-45.txt8.56 KBjsacksick
#45 commerce_shipping-create-admin-ui-for-shipments-2854035-45.patch67.92 KBjsacksick
#43 interdiff_40-42.txt4.31 KBjsacksick
#42 commerce_shipping-create-admin-ui-for-shipments-2854035-42.patch68.22 KBjsacksick
#42 interdiff_40-42.txt1.34 KBjsacksick
#40 commerce_shipping-create_admin_ui-2854035-40.patch65.65 KBjsacksick
#40 interdiff_39-40.txt15.46 KBjsacksick
#39 state-transition-form.png55.77 KBjsacksick
#39 commerce_shipping-create_admin_ui-2854035-39.patch64.75 KBjsacksick
#39 interdiff_38-39.txt2.29 KBjsacksick
#38 commerce_shipping-create_admin_ui-2854035-38.patch64.71 KBjsacksick
#38 interdif_37-38.txt7.36 KBjsacksick
#37 interdif_34-37.txt7.99 KBjsacksick
#37 commerce_shipping-create_admin_ui-2854035-37.patch61.42 KBjsacksick
#34 interdiff_30-34.txt6.45 KBjsacksick
#34 commerce_shipping-create_admin_ui-2854035-34.patch54.37 KBjsacksick
#33 30-26-interdiff.txt10.76 KBandyg5000
#33 commerce_shipping-create_admin_ui-2854035-30.patch50.43 KBandyg5000
#30 commerce_shipping-create_admin_ui-2854035-28.patch43.72 KBandyg5000
#30 28-25-interdiff.txt4.34 KBandyg5000
#28 commerce_shipping-create_admin_ui-2854035-27.patch43.43 KBandyg5000
#28 26-25-interdiff.txt4.05 KBandyg5000
#26 2854035-26.patch40.21 KBalexpott
#26 25-26-interdiff.txt1.14 KBalexpott
#25 2854035-21.patch40.22 KBalexpott
#25 20-21-interdiff.txt6.25 KBalexpott
#21 2854035-20.patch39.53 KBalexpott
#19 interdiff-16-19.txt595 bytesjosephdpurcell
#19 commerce_shipping_admin-ui-2854035-19.patch39.95 KBjosephdpurcell
#19 commerce_shipping_admin-ui-2854035-19-permissions-PATCHED.png72.64 KBjosephdpurcell
#19 commerce_shipping_admin-ui-2854035-19-permissions-NOT-patched.png56.26 KBjosephdpurcell
#18 commerce_shipping_admin-ui-2854035-BETA-4-18.patch39.18 KBjosephdpurcell
#18 interdiff-13-18.txt2.07 KBjosephdpurcell
#16 commerce_shipping_admin-ui-2854035-16.patch39.95 KBjosephdpurcell
#16 interdiff-15-16.txt2.11 KBjosephdpurcell
#15 interdiff-13-15.txt27.32 KBjosephdpurcell
#15 commerce_shipping_admin-ui-2854035-15.patch38.86 KBjosephdpurcell
#13 commerce_shipping_admin-ui-2854035-13.patch38.12 KBjosephdpurcell
#13 interdiff-12-13.txt1.42 KBjosephdpurcell
#12 interdiff-2854035-11-12.txt808 bytessweetchuck
#12 commerce_shipping_admin-ui-2854035-12.patch36.41 KBsweetchuck
#11 commerce_shipping_admin-ui-2854035-11.patch36.35 KBericchew
#11 interdiff-2854035-10-11.txt652 bytesericchew
#10 interdiff-2854035-6-10.txt9.36 KBericchew
#10 commerce_shipping_admin-ui-2854035-10.patch36.16 KBericchew
#8 commerce_shipping_admin-ui-2854035-6.patch33.18 KBericchew
#5 commerce_shipping-shipment-item.png40.56 KBctrladel
#5 commerce_shipping-shipments-list.png23.8 KBctrladel
#5 interdiff-2854035-2-5.txt16.21 KBctrladel
#5 commerce_shipping-admin-ui-2854035-5.patch22.44 KBctrladel
#2 commerce_shipping-admin-ui-initial-2854035-2.patch6.85 KBjsacksick

Comments

bojanz created an issue. See original summary.

jsacksick’s picture

It's far away from being done, but sending my work in progress (See https://cl.ly/3y2u1L271Z06), the patch contains the initial ShipmentListBuilder with the delete operation.

rgpublic’s picture

Just wondering: Is there, coincidentally, any progress in the mean time on this (e.g. a new patch)? If not, I'd need to quickly patch in some temporary solution for a current project.

bojanz’s picture

No progress, it would have been on the issue.

ctrladel’s picture

Took another pass at this. Changes in the patch

- Setup a canonical url for shipments at '/admin/commerce/orders/{commerce_order}/shipments/{commerce_shipment}'
- Created a view on the collection path '/admin/commerce/orders/{commerce_order}/shipments' that overrides the listbuilder , this mimics what is currently done for orders at '/admin/commerce/orders'
- Changed the default shipment view mode to display a lot more fields
- Created a field formatter that formats 'commerce_shipment_item' fields as a table
Shipments list

Shipment item

ctrladel’s picture

Issue summary: View changes

Removing the image I put in the issue summary by accident.

zerolab’s picture

Status: Active » Needs review
ericchew’s picture

StatusFileSize
new33.18 KB

Added:

  • Shipment Add/Edit Form
    • new/edit shipping profile for the shipment
    • Allows you to select OrderItems in the order and add them as ShipmentItems. Doesn't show order items that are already tied to other shipments
    • Recalculate button that repopulates the shipping methods available.
  • ShipmentController that handles routing based on whether multiple ShipmentTypes exist
  • Modified ShipmentListBuilder so that you can click on the title to view the shipment.

I only tested using Commerce_FedEx and FlatRates. Commerce_FedEx was set to package items individually, right now it errors out when its set to anything else. I have a fix for it but right now FedEx's test site is down so its hard to work on. I'll post an updated patch when I can.

ericchew’s picture

A couple more things:

  1. Need to change $form['items'] to required in ShipmentForm class. Without this, you get an error trying to create a new shipment if no shipment items were added to it. I will fix this in next patch.
  2. When saving a new shipment, should shipping adjustment be created and added to the order like it does when shipments are created through the cart?
  3. When deleting an order item, should we loop through shipments to see if it is being referenced and remove the item from the shipment? If the shipment only references that one item, should we delete the shipment?
  4. I think once https://www.drupal.org/node/2844920 is figured out it'd be nice to be able to select existing shipping profiles (if available) when adding a shipment.
  5. If an order item has multiple quantities, should you be able to split it between shipments?
ericchew’s picture

Added the ability to select the PackageType for the shipment. Made sure that any changes to the shipping profile address were being used when recalculating shipping.

Known Issues:
- In FedEx shipping method, you can configure whether shipments should be packaged all in one, individually, or calculated. This makes sense for the Checkout pane, because you want to display all of the shipments as one rate to the customer. However, that configuration setting also changes rate calculations when creating a shipment through the admin UI. If you have 3 shipment items in the shipment through the Admin UI, and FedEx is set to package items individually, the rate request is sent as if that shipment is 3 boxes. It's my opinion that when creating a shipment manually, shipment = one package, and you are specifying what items are in that package, so it should always calculate as "all in one box" when being created manually.

ericchew’s picture

Just noticed that you get "Illegal choice has been detected" message if no rates are available when calculating shipping. Patch sets shipping methods #access to false if no options are available.

sweetchuck’s picture

Status: Needs review » Needs work
StatusFileSize
new36.41 KB
new808 bytes

I think we should create a new field widget plugin for the "package_type" base field instead of define a form element in the \Drupal\commerce_shipping\Form\ShipmentForm::form.
Similar to the \Drupal\content_moderation\Plugin\Field\FieldWidget\ModerationStateWidget.

It is not sure there is a package_type saved to the shipment.
Only modification in this patch compared to the #11 is that checks that the package_type is empty or not.

josephdpurcell’s picture

Priority: Normal » Major
StatusFileSize
new1.42 KB
new38.12 KB

Moving priority to Major. Being able to create a shipment from the admin interface is a key feature of Commerce.

Confirmed that patch #12 works. I did not complete thorough testing or code review on it.

Attached is a small modification to #12 by adding a "Shipments" entity operation similar to what Payments does. See interdiff.

Juterpillar’s picture

Thanks everyone for you work on this. Patch #13 seems to be working as expected for me.

josephdpurcell’s picture

StatusFileSize
new38.86 KB
new27.32 KB

Reroll on latest 8.x-2.x.

josephdpurcell’s picture

Attempt to split out permissions on viewing shipment entities. This is completely untested! I copy / pasted from what I saw in orders.

Next step: test this and see if you can correctly control permission to CRUD operations on the Shipment entity, similar to how you can with Orders.

ericchew’s picture

I'm adding a related issue because it will most certainly impact the Admin UI. I advise anyone following this issue to take a look at https://www.drupal.org/project/commerce_shipping/issues/2953483 and check out the repo and video I posted. I have made significant changes to the Admin UI there.

josephdpurcell’s picture

Everyone can ignore this patch unless you're using beta 4. Since we are using beta 4, I needed to get a patch for it. It will match my follow up comment for latest 8.x-2.x.

josephdpurcell’s picture

Attached is a minor change to #16 to ensure the permission is grouped correctly. Consider this the most recent patch on this ticket.

Attached are also 2 screenshots showing the difference in permissions between 13 and 19. Here, you can see that there are now granular permissions on the Shipments. There is also a "View own shipments" permission, which could probably be removed? I'm 80% confident this patch isn't totally right, so if I need to revise I'll follow up.

To clarify: the problem I'm trying to solve with the permissions is to allow someone with Administer Shipments to be able to add a shipment to an order without also needing to have Administer Shipment Types permission.

finex’s picture

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new39.53 KB

I've re-rolled the patch on top of 8.x-2.x - just a small conflict in commerce_shipping.module's use statements.

Status: Needs review » Needs work

The last submitted patch, 21: 2854035-20.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

bojanz credited thejacer87.

bojanz’s picture

alexpott’s picture

Issue tags: +Needs tests
StatusFileSize
new6.25 KB
new40.22 KB

Coding standards clean-up.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new1.14 KB
new40.21 KB

Fixing the tests

andyg5000’s picture

I like where this is headed. The two pieces that I noticed are :

1) Shipping adjustment is not automatically added to order. Should there be a flag on the shipment to force this?

2) The entity reference on the order is not populated so the order is unaware of the shipment (e.g. doesn't show shipping address)

3) Variations that do no extend the shippable trait are included in the shipment UI

andyg5000’s picture

Updates to #26 to fix issues found in #27 attached

Status: Needs review » Needs work

The last submitted patch, 28: commerce_shipping-create_admin_ui-2854035-27.patch, failed testing. View results

andyg5000’s picture

Status: Needs work » Needs review
StatusFileSize
new4.34 KB
new43.72 KB

Status: Needs review » Needs work

The last submitted patch, 30: commerce_shipping-create_admin_ui-2854035-28.patch, failed testing. View results

andyg5000’s picture

Assigned: Unassigned » andyg5000
andyg5000’s picture

Assigned: andyg5000 » Unassigned
Status: Needs work » Needs review
StatusFileSize
new50.43 KB
new10.76 KB

Ok, here's a patch with all of the improvements mentioned in #27 + test coverage. I think this is pretty close to ready, but would like some extra eyes on it.

The remaining non-blocking (imo) things that should be addressed once this is settled are as follows.

1) Lock adjustments on order edit form when they are tied to a shipment
2) Provide ability to override shipment amount

jsacksick’s picture

I've been doing some manual testing today and expanded the patch a little:

First of all, IMO the shipments tab should only appear if the order type has the "shipping enabled".

Also, on my local installation, I didn't have any package types configured, and clicking on "Recalculate shipping" would eventually return a 500 (Because we're trying to instantiate a package type with an empty string).

I fixed these 2 things and I'm now wondering if we shouldn't add tests coverage for the shipments admin (although this would require more time...)

jsacksick’s picture

Btw, shouldn't commerce_shipping_entity_operation() check the "administer commerce_shipment" permission instead of "adminster commerce_shipment_type"?

alexpott’s picture

@jsacksick we definitely need to add test coverage here. Adding features without test coverage makes them liable to break.

jsacksick’s picture

I started writing a functional test for the ShipmentAdmin but it still needs to be expanded, posting my work in progress.

jsacksick’s picture

StatusFileSize
new7.36 KB
new64.71 KB

I expanded the ShipmentAdminTest and found 2 bugs while doing that:

1) There was a bug with the views caching (In the tests, we navigate to the shipments tab, add a shipment then save, then we get redirected to the shipments page again and the page was empty after adding a new shipment), so I configured the caching to "none" (I don't think there's a point in caching that view anyway)....
2) The logic in ShipmentForm wasn't actually adding existing shipment items to the $shipment_item_options array, that was done from the $order_items loop, the problem with that is that the order item label was used instead of the actual shipment item title that we get by calling $shipment_item->getTitle(), that could probably be confusing.

Besides that, I think we can commit the patch as is, and probably work on the remaining things that @andyg5000 mentioned in comment #33 after this is committed (in follow-up issues, because the patch is already big enough).

jsacksick’s picture

StatusFileSize
new2.29 KB
new64.75 KB
new55.77 KB

Ok, actually, I realized we forgot "2) Perform state transitions on the created shipments.", the attached patch is taking care of that (we now output the transition form instead of the state label, see screenshot below):

jsacksick’s picture

StatusFileSize
new15.46 KB
new65.65 KB

Ok, sorry for the noise :p, but realized we don't need all these changes in the routing.yml and we were directly calling in several places the entity class for creating/loading entities (i.e Profile::create or OrderItem::load).

I made some changes to the ShipmentRouteProvider, as well as the ShipmentController which now extends the EntityController, I'm implementing a different method for the "add-page" though since we have to pass the order ID to the shipment add form.

Additionally ShipmentForm was still passing the entity manager, which we were still using in many places although it's deprecated...

I hope we're good to go this time!

alexpott’s picture

+++ b/config/install/views.view.order_shipments.yml
@@ -2,9 +2,9 @@ langcode: en
-    - commerce_price
-    - commerce_shipping
-    - options
+  - commerce_price
+  - commerce_shipping
+  - state_machine

@@ -412,11 +412,11 @@ display:
-        - 'languages:language_content'
-        - 'languages:language_interface'
-        - url
+      - 'languages:language_content'
+      - 'languages:language_interface'
+      - url

@@ -427,9 +427,9 @@ display:
-        - 'languages:language_content'
-        - 'languages:language_interface'
-        - url
+      - 'languages:language_content'
+      - 'languages:language_interface'
+      - url

These change does not look right - has the view been hand edited?

jsacksick’s picture

StatusFileSize
new1.34 KB
new68.22 KB

Minor changes to the view:

  • Add an empty text to the view ("There are no shipments yet").
  • Sort by shipments created DESC.
  • Make the title & amount columns sortable.
jsacksick’s picture

StatusFileSize
new4.31 KB

Attaching the right interdiff between #40 & 42.

alexpott’s picture

  1. Some thoughts... not a complete review
  2. +++ b/commerce_shipping.module
    @@ -200,3 +202,23 @@ function commerce_shipping_preprocess_commerce_order_receipt(&$variables) {
    +  if ($entity->getEntityTypeId() !== 'commerce_order' ||
    +    ($entity->hasField('cart') && $entity->get('cart')->value) ||
    +    !\Drupal::currentUser()->hasPermission('administer commerce_shipment')) {
    +    return;
    +  }
    

    I think a comment explaining this if would be great. Also do we have test coverage?

  3. +++ b/src/Access/ShipmentCollectionAccessCheck.php
    @@ -0,0 +1,69 @@
    +    if (!$order) {
    +      return AccessResult::forbidden();
    +    }
    
    +++ b/src/Entity/Shipment.php
    @@ -43,6 +55,14 @@ use Drupal\profile\Entity\ProfileInterface;
    + *     "collection" = "/admin/commerce/orders/{commerce_order}/shipments",
    
    +++ b/src/ShipmentRouteProvider.php
    @@ -0,0 +1,64 @@
    +      $route->setOption('parameters', [
    +        'commerce_order' => [
    +          'type' => 'entity:commerce_order',
    +        ],
    +      ]);
    +      $route->setRequirement('_shipment_collection_access', 'TRUE');
    

    I don't think the if (!$order)... is necessary - with no order this route will not check access. It'll 404.

  4. +++ b/src/Access/ShipmentCollectionAccessCheck.php
    @@ -0,0 +1,69 @@
    +    $order_type_storage = $this->entityTypeManager->getStorage('commerce_order_type');
    +    /** @var \Drupal\commerce_order\Entity\OrderTypeInterface $order_type */
    +    $order_type = $order_type_storage->load($order->bundle());
    +    $shipment_type_id = $order_type->getThirdPartySetting('commerce_shipping', 'shipment_type');
    +    if (empty($shipment_type_id)) {
    +      // Order types with no shipment type selected don't need the Shipments
    +      // tab.
    +      return AccessResult::forbidden()->addCacheableDependency($order_type);
    +    }
    +
    +    $permissions = [
    +      'administer commerce_shipment',
    +    ];
    +    return AccessResult::allowedIfHasPermissions($account, $permissions);
    

    I think this won't result in the right cacheability and is not as clear as it could be. Something like:

        /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
        $order = $route_match->getParameter('commerce_order');
        $order_type_storage = $this->entityTypeManager->getStorage('commerce_order_type');
        /** @var \Drupal\commerce_order\Entity\OrderTypeInterface $order_type */
        $order_type = $order_type_storage->load($order->bundle());
        $shipment_type_id = $order_type->getThirdPartySetting('commerce_shipping', 'shipment_type');
    
        // Only allow access if order type has a corresponding shipment type.
        // @todo should we validate that the shipment type exists.
        return AccessResult::allowedIf($shipment_type_id !== NULL)
          ->addCacheableDependency($order_type)
          ->andIf(AccessResult::allowedIfHasPermission($account, 'administer commerce_shipment'));
    

    Also is there test coverage for the case where the order type does not have a shipment type?

  5. +++ b/src/Controller/ShipmentController.php
    @@ -0,0 +1,115 @@
    +    // If only one shipment type is available, bypass the bundle selection.
    +    if (count($content) == 1) {
    +      $type = array_shift($content);
    +      return $this->redirect('entity.commerce_shipment.add_form', [
    +        'commerce_order' => $order_id,
    +        'commerce_shipment_type' => $type['type']
    +      ]);
    +    }
    

    Is there test coverage of this logic? Either where there is one of more shipment types?

  6. +++ b/src/Entity/Shipment.php
    @@ -357,6 +386,69 @@ class Shipment extends ContentEntityBase implements ShipmentInterface {
    +      $adjustment_exits = FALSE;
    ...
    +            $adjustment_exits = TRUE;
    ...
    +      if (empty($adjustment_exits)) {
    

    exists - also given we know this is boolean then if (!$adjustment_exists) is fine - the empty() makes you ponder why.

  7. +++ b/src/Form/ShipmentForm.php
    @@ -0,0 +1,310 @@
    +        in_array($order_item->id(), $already_on_shipment)) {
    

    in_array() - let's make it type safe using the third param - set it to TRUE.

  8. +++ b/src/Form/ShipmentForm.php
    @@ -0,0 +1,310 @@
    +    // Add the shipment to the order if it doesn't exits.
    

    exist

  9. +++ b/src/OrderShipmentSummary.php
    @@ -40,6 +40,10 @@ class OrderShipmentSummary implements OrderShipmentSummaryInterface {
         $shipments = $order->get('shipments')->referencedEntities();
         $first_shipment = reset($shipments);
    +    // Make sure the shipment still exists.
    +    if (empty($first_shipment)) {
    +      return [];
    +    }
         $shipping_profile = $first_shipment->getShippingProfile();
    

    Won't it be clearer to check if shipments is empty? Is the reason for this code tested?

jsacksick’s picture

StatusFileSize
new67.92 KB
new8.56 KB

@alexpott: Thanks a lot for this review! It turns out some more changes were actually required to make it right.

I had some discussions with @bojanz today and made the following changes:

  • We're no longer manually adding shipping adjustments, but we now trigger an order refresh so that the adjustment is added by the processor during the refresh process (Manually adding adjustments isn't a good idea since they'd be blown away by OrderRefresh).
  • I moved the delete() logic to postDelete() and also trigger an order refresh there so that the shipping adjustments are removed automatically.

I'm just posting my progress here, but the patch is actually still incomplete... There's an issue with the Access checker which doesn't seem to be called for some reason (I remember it working which is kind of driving me crazy...).

Also, we're still missing test coverage to make sure the "Shipments" tab / operation isn't shown for cart orders...

Status: Needs review » Needs work
jsacksick’s picture

Status: Needs work » Needs review
StatusFileSize
new16.05 KB
new72.29 KB

I've continued working on this today and made once again several changes:

  1. I realized the assertions that were introduced in #33 to the ShipmentTest were never evaluated because the order doesn't have adjustments.
  2. I removed the postSave() logic and moved that to the save() method in ShipmentForm (because in other places, we usually save the order after adding the shipments to the order (or that's done by the refresh process automatically), with the postSave() logic the order would be saved everytime a shipment is saved, so in a loop that'd potentially mean saving the same order multiple times in a row.
  3. I finally found out why the route requirement I'm adding (_shipment_collection_access) didn't seem to be in place, it was caused by Views which is altering the route and not merging the route settings in PathPluginBase::alterRoutes(), so I had to implement a route subscriber to put back the route requirement that makes sure the shipments tab isn't shown for cart orders & non shippable orders.
  4. I expanded the test coverage, I added tests for making sure that the shipments tab isn't shown for cart orders / non shippable orders.
  5. I updated the logic in ShipmentController::addShipmentPage() because I realized that there's no point in showing a "shipment types" list since there's an order type setting that lets you configure the shipment type per order type, so the logic now redirects to the add shipment page for the configured shipment type.

I'm wondering if we need to prevent updating shipments that are "owned_by_packer" (that flag is added by the packer), because a modification to a shipment that's owned by the packer could be blown away (We might have to just forbid updating the "shipment items" field in that case).
The problem with doing that is that could make this entire UI useless (Although you might want to perform updates to shipments that are owned by the packer once the order is placed).

At least we're forbidding access to the shipments tab for cart orders, which is probably sufficient.

alexpott’s picture

StatusFileSize
new1.63 KB
new71.36 KB

I've moved the unrelated code style changes to #3013669: Update phpcs to 3.x so people don't have to ponder why they are here. Also fixed the typehints in #47 which had some duplication.

alexpott’s picture

StatusFileSize
new4.9 KB
new69.31 KB
  1. +++ b/commerce_shipping.module
    @@ -200,3 +202,26 @@ function commerce_shipping_preprocess_commerce_order_receipt(&$variables) {
    +  // Show the "Shipments" operation link for orders, if the current user
    +  // has the "administer commerce_shipment" permission and if the order is not
    +  // a "cart" order.
    +  if ($entity->getEntityTypeId() !== 'commerce_order' ||
    +    ($entity->hasField('cart') && $entity->get('cart')->value) ||
    +    !\Drupal::currentUser()->hasPermission('administer commerce_shipment')) {
    +    return;
    +  }
    

    Given this is access related I think it is better to split this out into separate ifs. It's much clearer as to what is going on and helps lessen the cognitive load of working out what's happening.

  2. +++ b/src/Access/ShipmentCollectionAccessCheck.php
    @@ -0,0 +1,66 @@
    +    /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
    +    $order = $route_match->getParameter('commerce_order');
    +    $order_type_storage = $this->entityTypeManager->getStorage('commerce_order_type');
    +    /** @var \Drupal\commerce_order\Entity\OrderTypeInterface $order_type */
    +    $order_type = $order_type_storage->load($order->bundle());
    +    $shipment_type_id = $order_type->getThirdPartySetting('commerce_shipping', 'shipment_type');
    +    // Check if this is a cart order.
    +    $order_is_cart = $order->hasField('cart') && $order->get('cart')->value;
    +
    +    // Only allow access if order type has a corresponding shipment type.
    +    // @todo should we validate that the shipment type exists?
    +    return AccessResult::allowedIf($shipment_type_id !== NULL)
    +      ->andIf(AccessResult::allowedIfHasPermission($account, 'administer commerce_shipment'))
    +      ->andIf(AccessResult::allowedIf(!$order_is_cart))
    +      ->addCacheableDependency($order_type)
    +      ->addCacheableDependency($order);
    

    This is very readable. Nice. It really helps when access related stuff is easy to grok.

  3. +++ b/src/EventSubscriber/RouteSubscriber.php
    @@ -0,0 +1,40 @@
    +    // @todo: Remove once Views will merge the route requirements.
    

    Is there a core issue for this?

  4. We can have less code in the controller.
  5. I think that ShipmentListBuilder is completely untested. It's the non-views fallback for the shipments listing page.
alexpott’s picture

Actually point #49.5 appears to be wrong. We've no test with views installed as far as I can see.

alexpott’s picture

StatusFileSize
new2.71 KB
new71.62 KB

Lol actually #49.5 was correct. The Views module is required by the Commerce module. Here's a test to prove the listing works with and without the view.

Status: Needs review » Needs work

The last submitted patch, 51: 2854035-51.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new553 bytes
new70.92 KB

Hmmm some unrelated change crept in :(

alexpott’s picture

StatusFileSize
new375 bytes
new70.92 KB

Small coding standards fix.

alexpott’s picture

@smccabe review this and suggest the UI needed a little polish but that it could be done in a follow-up - so I've created #3015345: Improve shipment admin UI look and feel

bojanz’s picture

Status: Needs review » Needs work

Review, part 1:

1.

+      '#title' => 'Shipment Items',

Not using $this->t(). Weird case (should be "Shipment items"). Is there a friendlier label we could use in general?

2.

$address = $form_state->getValue([
+        'shipping_profile',
+        '0',
+        'profile',
+        'address',
+        '0',
+        'address',
+      ]);

This is odd, we never break up a parents array per-row like this. Let's just declare $parents on the preceding line.

3.

+    // Check if the shipment amount has changed, if so we need to trigger
+    // an order refresh so that the shipping adjustment gets adjusted.
+    if ($form_state->get('original_amount') != $shipment->getAmount()) {
+      $order->setRefreshState(OrderInterface::REFRESH_ON_SAVE);
+      $save_order = TRUE;
+    }

This feels like logic that should live on the shipment entity itself. Esp since we're already doing the same in Shipment::delete().

4.

+ *   id = "commerce_shipping_shipment_item_table",

We generally prefix plugins with just commerce_, so this would be "commerce_shipment_item_table".

5.

+    $shipmentItems = $items->getShipmentItems();
+    foreach ($shipmentItems as $delta => $item) {

Unexpected camelCase.

6.

+class ShipmentPermissionProvider extends EntityPermissionProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildPermissions(EntityTypeInterface $entity_type) {
+    $permissions = parent::buildPermissions($entity_type);
+    // Shipments don't implement EntityOwnerInterface, so they don't get
+    // own/any permissions generated by default.
+    $permissions['view commerce_shipment']['title'] = (string) t('View any shipment');
+    $permissions['view own commerce_shipment'] = [
+      'title' => (string) t('View own shipments'),
+      'provider' => 'commerce_shipping',
+    ];
+
+    return $permissions;
+  }

Permission added but never used. Shipments don't even have a uid field, so this doesn't make sense.

7. The "Shipments" operation is shown in the list builder even if the page is not available (cause the order is still a cart). So I click it and get a 403. Look at how we fixed this in the ProductListBuilder.

8. ShipmentListBuilder injects the deprecated numberFormatter, instead of using the newer currencyFormatter. Plus, the numberFormatter property is never declared.

9. There's a tracking code column in the table, but no tracking code field on the edit form. We either need to fix the field or remove the column.

alexpott’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new8.97 KB
new70.58 KB
  1. Fixed. We already have shipment item in the UI. See \Drupal\commerce_shipping\Plugin\Commerce\ShippingMethod\FlatRatePerItem::buildConfigurationForm() I guess we should add a follow-up to discuss how to rename that if you want to do that.
  2. Fixed
  3. @todo - so here should we move all the order saving logic into Shipment::save(). I'm a little dubious of this because saving other entities during another entity save is often very very problematic.
  4. Fixed
  5. Fixed
  6. Let's get rid of ShipmentPermissionProvider entirely. The permissions are not used at all. Less code FTW and if we need a viewer permission we can add that later.
  7. I couldn't make this happen. I created a cart order and then went to admin/commerce/orders/carts and shipments is not in the list. There is code in commerce_shipping_entity_operation() to prevent this. I've added test coverage because I couldn't see any.
  8. Fixed
  9. @todo - I've asked @bojanz which way to go here. Manually editing tracking codes feels funny. Maybe that's why it is not on the edit form but showing them to the operator feels correct.
jsacksick’s picture

I agree with @alexpott on 3) I didn't want to put the order refresh logic in a postSave() as I think this could potentially be very problematic. Let's say you have custom code that adds multiple shipments to the order, you'll most probably save the order yourself and you wouldn't want the order to be saved each time a shipment is saved (from within a loop for instance).

ShipmentOrderProcessor itself loops on the shipments that are returned by the packer and save them in case changes were detected (so multiple shipments are returned, the same order would be saved many times).
Furthermore, the order processors are called from OrderRefresh::refresh() which is itself called from doOrderPresave() on the order storage so I'm sure we'd like to prevent the order to be saved inside a presave.

alexpott’s picture

StatusFileSize
new528 bytes
new71.05 KB

Discussed the tracking code on the list builder with @bojanz in Slack. His recommendation was to remove it. I then convinced him to keep it and make it displayed by setting display options in \Drupal\commerce_shipping\Entity\Shipment().

This turns out to be complex. Shipping methods only support tracking codes if they implement \Drupal\commerce_shipping\Plugin\Commerce\ShippingMethod\SupportsTrackingInterface. However as @bojanz pointed out in slack, there's nothing to stop a user

sell(ing) something with flat rate shipping, get a tracking code at the post office, enter it in the UI

Therefore the patch attached sets the dispaly options for the tracking code so users can edit it. Given that this is all standard field API stuff I've not added any test coverage.

We need a follow-up to make the commerce_tracking_link formatter support cases where the shipping method does not have SupportsTrackingInterface and then we can make the listbuilder and view use it for enhanced usability. I'll create the issue.

  • bojanz committed 7f02042 on 8.x-2.x authored by alexpott
    Issue #2854035 by alexpott, jsacksick, josephdpurcell, andyg5000,...
bojanz’s picture

Status: Needs review » Fixed
StatusFileSize
new303.16 KB

Thanks everyone. Time to commit this issue and continue in followups.

I've made the following visual tweaks pre-commit:
1) Moved the title to the top of the form. It's odd to have it in the middle of the form, after the profile, Drupal never does that.
2) Added a fieldset around the profile, to group the address and related fields.
3) Moved the tracking code field to the bottom, to provide a buffer between "Recalculate shipping" and the main action buttons.
Ultimately the recalculation will need to be made automatic, but this at least makes it look less ugly.
4) Cleaned up the code a bit (made the elements on the form defined in the order of their weights).
See attached screenshot.

melpers’s picture

I've run into some issues with this fix when used in conjunction with Commerce UPS and the checkout flow is setup for the customer to pick a shipping option during checkout.

1) When editing the shipment after checkout it will allow you change boxes and remove items, and then recalculate the shipping cost. However, saving the shipment and then going back into edit again none of the changes are reflected (The new shipping total is correct though). Making the same changes a second time and saving it again seems to resolve this.

2) If a customer gets to the review order page (i.e. post-shipping selection) and then edits the cart to add/remove/update items and/or quantities resulting in a new shipment total the next time they go through checkout then when viewing the order's Shipments tab it lists all of the shipments that had been on the order at some point. The last one selected has Send & Cancel options under State, and the others will have Finalize & Cancel options.

3) There is no way to split an item with more than 1 quantity across shipments.

4) If you update a shipment's cost it will be reflected in the order's totals. This does not seem to be a desired behavior after the customer has already paid.

I don't know if any of this might be an issue for the UPS Module to address, but it all felt like core shipping issues.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

alexpott’s picture

@MElpers thanks for the comment.

  1. I've tried to make this occur with UPS enabled and configured but I can't make it happen. Can you detail the exact steps - thanks!
  2. I've tried to reproduce this but I can't make it occur either. Do you have exact steps?
  3. I've opened #3025093: Allow splitting an item with a quantity > 1 across shipments to work on that functionality
  4. I've opened #3025096: Changing a shipment after the customer has paid recalculates the total - it'd be great if you can describe what you think the behaviour should be on that issue.
melpers’s picture

StatusFileSize
new34.15 KB
new58.27 KB
new22.16 KB
new32.72 KB
new23 KB
new40.89 KB

@Alexpott thanks for the two new issues. I'll follow those and comment on #4 shortly.

For the first two, hopefully this will help. First, I ran all of these on fresh install with none of our customer's custom code. I just setup 2 products with a single variation each. The modules I'm testing with are:

  • Core 8.6.4
  • Commerce 2.11
  • Commerce Payeezy: 1.0-beta1
  • Commerce Connector for AvaTax 1.0-beta4
  • Commerce UPS: 3.0-dev

I can provide the patch list too if you need it.

Issue #1:

  1. Place an order for 1 item.
  2. Go to the Shipment Tab and Edit the shipment. See Example1a.jpg.
  3. Note the shipping amount & package type. See Example1b.jpg.
  4. Change the package type and click Recalculate Shipping. (You may need a custom package setup). See Example1c.jpg. Save it.
  5. Back on the Shipments tab, note that the Amount is now the new re-calculated value. See Example1d.jpg.
  6. Edit the shipment again.
  7. The form has the original shipping cost & box options. See Example1e.jpg.

I can also recreate this when there are multiple items in the cart and 1 or more of the item checkboxes are unchecked & then the shipping is recalculated.

Running through the same steps a second time to get the same new shipping totals & saving it again will then allow the new values to show up if the shipment is edited a third time.

Issue #2:

  1. Add 1 of item # 1 to my cart.
  2. View Cart > Checkout
  3. Fill out shipping & payment if needed. Select UPS Ground. Continue to Review.
  4. Confirm the Summary has the shipping & tax information.
  5. Return to the site and add 1 of item #2 to the cart.
  6. View Cart > Checkout
  7. Increase the quantity of item #2 to 2 and click the Update Cart button.
  8. Fill out shipping & payment if needed. Select UPS Ground. Continue to Review.
  9. Pay and Complete purchase.
  10. Open Commerce Orders page and then open the Shipments tab. See Example2a.jpg.

Let me know if you need any additional info.

alexpott’s picture

@MElpers can you confirm what version of commerce_shipping you are on? Are you on a beta version with the patch applied or have you checkout the commit? Because the first problem sounds a lot like #2920242: Shipment always using last shipping method's package type

melpers’s picture

Composer tells me it is 2.x-dev 7f02042. I am applying patches for

Multiple destination shipping: https://www.drupal.org/files/issues/2018-10-11/2961463-29.patch
Auto-recalculate shipping when the address changes: https://www.drupal.org/files/issues/2018-10-11/2849756-83.patch
Commerce_profile_select reusable profiles: https://www.drupal.org/files/issues/2018-10-05/2948954-35.patch

alexpott’s picture

Yay I've managed to recreate #1 there appears to be some random-ness but if you change packages and recalculate shipping enough times eventually it will go wrong - I'm investigating the bug.

alexpott’s picture

alexpott’s picture

I'm addressing #66.2 in #2994345: Remove shipping when cart is emptied - this issue has kind of been reported before.

alexpott’s picture

@MElpers I might be wrong in #71. I'm struggling to reproduce the issue but via reading the code I've come up with #3033195: Need to repack when number of items changes