Customer Shipment Autopack

I am using a first draft for an extended Customer Shipment pack transition,
which auto-packs according to rules in the Autopack Model.

For me it is fast enough, but probably too hacky and probably does not really respect the Workflow of Shipment for inclusion in Tryton. (Thus posted in Message Board rather than Feature).
If someone thinks this Model has potential, I will be more than happy to contribute my best for inclusion.
The issue for me has always been that SAO on my mobile device is not adequate to use in the warehouse,
so I end up creating all the labels in the office, then I go out and Pick → Pack → Stick the labels on the boxes.

This hack helps me speed up my workflow for carriers/customers who have a well defined packing.

from trytond.model import ModelSQL, ModelView, Workflow, fields
from trytond.model import MatchMixin,sequence_ordered
from trytond.pool import Pool, PoolMeta

class Autopack(ModelSQL, ModelView, sequence_ordered(), MatchMixin):
    __name__ = 'stock.package.autopack'

    # Match Outgoing Moves
    product = fields.Many2One(
        'product.product', "Product", required=True)
    carrier = fields.Many2One('carrier', "Carrier")
    party = fields.Many2One('party.party', "Party")

    # and pack them according to
    package = fields.Many2One(
        'stock.package.type', "Package", required=True)
    quantity = fields.Float(
        "Quantity", digits='unit', required=True,
        domain=[('quantity', '>', 0)])
    unit = fields.Function(
        fields.Many2One('product.uom', "Unit"),
        'on_change_with_unit')

    @fields.depends('product')
    def on_change_with_unit(self, name=None):
        if self.product:
            return self.product.template.default_uom
        return None

class ShipmentOut(metaclass=PoolMeta):
    __name__ = 'stock.shipment.out'

    @classmethod
    @ModelView.button
    @Workflow.transition('packed')
    def pack(cls, shipments):
        pool = Pool()
        Package = pool.get('stock.package')
        Autopack = pool.get('stock.package.autopack')
        autopacks = Autopack.search([])
        for shipment in shipments:
            to_pack = []
            for move in list(shipment.outgoing_moves):
                if move.state == 'cancelled' or move.package:
                    continue
                pattern = {
                    'product': move.product.id,
                    'party': shipment.customer.id,
                    'carrier': (
                        shipment.carrier.id
                        if getattr(shipment, 'carrier', None) else None),
                    }
                for autopack in autopacks:
                    if not autopack.match(pattern):
                        continue
                    pieces = move.split(
                        autopack.quantity, move.product.default_uom)
                    to_pack.extend(
                        (piece, autopack.package) for piece in pieces)
                    break
            for move, package_type in to_pack:
                package = Package(
                    company=shipment.company,
                    shipment=shipment,
                    type=package_type,
                    moves=[move],
                )
                package.save()
        super().pack(shipments)

Could you explain why?

I prefer the autopack approach because it matches our constraint (carrier max 31 kg / package), so often multiple packages per shipment.
Benchmark: manual packing of 2 packages ≈ 1 min; with 30–50 daily splits this is slow even in GTK (about twice as fast as SAO on mobile phone)
With the rule table I just Pack → Create shipping labels; Tryton prints which box gets which label.
All labels ready in ≤ 5 min, then one warehouse trip.

I guess we could have an automatic split to a quantity configured per product.

Otherwise I think that the idea of having the system suggest a possible packaging automatically sound appealing.
But I think it should be a suggestion that the user accept or not. And if he accepts the wizard do whatever it needs (split, unsplit etc.) to create the package and if some product still need to be packaged, a new suggestion is proposed.
So this way the packager still validate what it actually packaged but if rules can be defined he has less entries to make.

Packing is a complex process, and I think trying to make a universal solution that auto-packs or offers suggestion for every user shipment is too complex. I am convinced this is the reason why this functionality is rare. For this reason I would like to keep the logic as simple as possible, but not simpler.

In the Autopack model we could have a dropdown: “Split”, “Pack”, “Auto Pack” which could govern the function of the existing Pack Wizard.
In picked state:

  • If there is no rule, Pack Wizard would function like it does now
  • If there is a rule with split, it would split the outgoing moves (on entering picked state?)
  • If Pack is selected, then the Pack Wizard suggest the next package and packing.
  • If Auto pack is selected, then the Pack wizard split and pack the moves without user confirmation, the user can anyway inspect the results before transitioning to packed.

This way we can serve both the unsuspecting user, and the power user who wants automatic packing.

I am unsure how to make this work with the MatchMixin. Having one field does not allow invalid combinations, but makes matching using matchmixin diffifult, and having multiple booleans (ex. split, pack, autopack) would allow invalid combinations.

For me this feature along with functions to get number of packages for a shipment is essential to automate e-commerce as all carriers provide a price per package, so if I do not know how many packages a sale has, it is impossible to provide a shipping price. This feature can aid in both packing shipments and also making a shipping_cost calculation easier.

I see how unsplit could be useful, but it feels like we are pushing complexity