This question is a bit more complex, at least in my mind. So I start this with a couple of assumptions from our business world (which I think are applicable to others easily)
Since every order gets one shipment per default, initially (right after checkout is completed) there will never be an order-item-unit with no shipment or with a shipment that is not in state 'ready'
One shipment is bound to one tracking-identifier. If you send with more than one tracking-identifier you'd want more than one shipment.
A shipping intention can ultimately be defined as: I want to ship [item -> qty]
A partial shipment is not determined by Sylius. A partial shipment is a shipment on an order NOT containing every item-unit on the order.
Please help me out :)
You can programatically create as many shipments as you want.
By default it's done during the checkout in \Sylius\Component\Core\OrderProcessing\OrderShipmentProcessor.
You can replace it with a custom implementation or add complementary processors to change how the shipments are created during the checkout.
Alternatively, you could make it possible to manage order shipments from the admin panel.
For example a "Split/Partial" action button for the shipments grid, which will split the current shipment items between two shipments: one with the quantities you want to ship now and another with the remaining.
Or choose in how many shipments you want to split, or integrate it with a 3rd party system, or customize the order page to allow more more shipment related actions.
There are virtually unlimited possibilities.
It should be quite easy to implement any of that in the admin panel reusing the existing code (mostly the resource layer): forms, controllers, etc.
Sylius even has an order shipping state machine transaction for that: https://docs.sylius.com/en/1.6/book/orders/orders.html#state-machine-of-shipping-in-an-order
Generally, it has the base for multi-shipment orders, but the practical implementation is up to you.
Hey @vvasiloi - thanks for your reply.
I am actually aware how to manipulate the order processors and hook into the checkout process. We actually use shop-api, which makes this process a lot easier.
My question was more aimed towards an actual implementation/idea how to solve the problem of identifying the "last" shipment in the current sylius world, because I want to stick as close to sylius as possible. Or in other words, creating shipments based on data off an external system without leaving dead rows in the database or anything like that :)
Maybe others are trying to solve the same problem with the current mechanics, that shipments provide (handling many shipments per order, etc) and we can extend Sylius in a meaningful way, if this gains any traction.
Hooking into the order-processors with a switch (create default shipment yes/no) on the channel would be the way to go for me, if I decide to tackle this by overriding Sylius features in the end :)
The Sylius\Component\Core\OrderProcessing\OrderShipmentProcessor::processShipmentUnits actually shows how to not leave "dead rows".
The order item units are also the shipment units, so you can easily filter the units that were not shipped yet:
$unitsToShip = $order->getItemUnits()->filter(function (OrderItemUnitInterface $unit) {
if (null === $shipment = $unit->getShipment()) {
return true;
}
// I prefer to add an isCancelled() method
if ($shipment->getState() === $shipment::STATE_CANCELLED) {
return true;
}
return false;
});
This is just an example, the actual checks to perform depend on your business needs.
After writing this, I want to apologize in advance. I have slept extremely bad and what I have written may sound like I don`t appreciate your help, but I do. :) This is just the best I am able to explain my thoughts after this f*cked sleep. :(
That very same processor actually just creates a shipment per order if the order has an item that requires shipping. At that point in time, I don't have any information about how many shipments there will be or which items belong to what shipment, because this comes from an external system.
The method you mentioned is private, which means I have to call the process() method of the processor, which does more things, than I actually want. For example to get the existing shipment, it just takes the first one of the list of shipments. What if there is more than one shipment and each with different methods?
Also, the dead rows are not in the item-units, but currently its the default shipment that gets created.
1) Shipment with every item gets created
2) With multiple shipment intentions I begin to take items out of that first shipment and assign them to a new shipment that will be shipped.
3) At some point, the first shipment is either going to be empty (dead-row) or I have to determine if its the last shipment and ship it, so no dead rows will be left.
You don't have to use the default shipment processor.
I don't know when and how you need to create the right shipments, so I can't give any implementation details.
You could call the external service in a custom shipment processor or when the order is placed or when an admin triggers it.
The point is you can manipulate the shipments however you want.
The order items will be there no matter what you do with the shipments, so you can always re-iterate and assign to a shipments the items that don't have one.
Just to confirm - if I'd delete the OrderShipmentProcessor - everything else would just work as usual and I would not have shipments in my system?
If that is the case (actually I would replace the default one with a custom one as you described) - how would I trigger this. Can I just use my OrderShipmentProcessor directly in a service and call process() or do I have to somehow call the whole list of order-processors (which might have side-effects).
I'm not sure that everything will work as usual. The select shipping checkout step will probably break without at least one shipment.
If you register a custom order processor it will be added to the list, but you can use it separately as well.
If you register a custom order processor it will be added to the list, but you can use it separately as well.
Thanks for that. I wasn't sure if the processors are intended to be used separately. :)
I'm not sure that everything will work as usual. The select shipping checkout step will probably break without at least one shipment.
Thats a good point - but since we use shop-api, we might find a nice way around that. In my opinion, actual shipments should not be relevant for the ordering anyways. :)
Just in general - thank you so much for your patience <3 I was not in the best mood during that and you still helped me out. I appreciate that a lot! :)
In my opinion, actual shipments should not be relevant for the ordering anyways.
It might not be relevant it your case, but in general it is relevant.
Without that the user can't choose a shipping method.
The shipping methods provider returns the supported methods based on the shipment.
The default provider also uses the shipping method eligibility checker to filter the methods.
And there are probably more dependencies...
Anyway, you don't have to worry about that, because you use the shop api and shipments with shipping methods are provided by an external system.
thank you so much for your patience
You're welcome!
I suggest you to close the issue and use Slack/Forum/Stackoverflow if you need additional help.
P.S. I didn't see anything wrong in your messages.
It might not be relevant it your case, but in general it is relevant.
Without that the user can't choose a shipping method.
The shipping methods provider returns the supported methods based on the shipment.
The default provider also uses the shipping method eligibility checker to filter the methods.
And there are probably more dependencies...
One last thing I'd like to stick with in this issue is this - I think the shipment should be irrelevant and that shipping methods should be decoupled from shipments.
The shipping method is a vital bit of information that needs the eligibility check, area checks and more - but a shipment should be something different with no ties to the order or the shipping method, except for a "Can we actually use the selected shipping method to physically deliver these items" sort of check. :D
Just my two cents ;)
Thanks again! :)
@kortwotze As I already mentioned, it may not be relevant in your case, but in general, the availability, cost and other aspects of the shipping methods depend on the shipment.
If you go with the "Can we actually use the selected shipping method to physically deliver these items" approach, when a selected shipping method cannot be used to deliver the order items, the customer will have to get back and choose another, but it can be a wrong one again and again.
That's why Sylius has a provider with an eligibility checker on top, to only show the customer methods that he can actually use.
I beg to differ, I think the eligibility, cost and other factors depend on the items and the shipping-address of the order. A shipment should represent the physical package that is on the way to the customer after every check has been passed.
(With this I am suggesting to actually change the shipment-behavior in sylius :wink: )
Changing shipment method should totally be fine if you create the actual shipment when you actually pack the package. But this might be something for a different talk or table ;)
Thanks again :slightly_smiling_face:
How to choose the shipping method for a shipment if it's created when it's actually packed, I assume after the checkout is completed?

Selecting the shipping method should be interpreted as the "desire of the customer to pay X for shipping or to have it delivered faster/slower/whatever". The interface in your screenshot can stay the same, at least for single shipments per order. If you let your customers decide how the items should be packed and delivered (Amazon sometimes does this), than the behavior is different of course. In that case, one could actually create shipments in sylius which have to be fulfilled by shop-owners/logistics-teams. But even then, one could decouple shipping-methods and their selection from the actual shipments. :slightly_smiling_face:
From there, the shipping methods know how much they cost, how long they take and for which destinations they can be used. If the shop says "Yupp, we can ship these orders to that destination with that carrier", the order is accepted.
When the items are actually packed, you create a shipment for the order, using the method that has been checked and selected, add the tracking number and off it goes.
The information, which shipping method to use for a shipment can, oversimplified, be stored as a simple string on the order and that`s it. You later create the shipment, assign the selected method and you are done.
At least thats my opinion/experience when handling shipping. :wink:
@kortwotze yes I agree with you.
we can easily do this if you want to extend.
I love the "do nothing". That might actually do the trick for us. We have not yet implemented what I described so long ago due to ... planning reasons :joy: Thanks for the idea :)
This issue has been automatically marked as stale because it has not had any recent activity. It will be closed in a week if no further activity occurs. Thank you for your contributions.