Handling and transforming Operations

Model.Wms.Operation.Move

This Operation Model inherits from Mixin.WmsSingleInput

class anyblok_wms_base.core.operation.move.Move[source]

A stock move

Fields and their semantics

id = <anyblok.column.Integer object>
destination = <anyblok.relationship.Many2One object>

Specific members

revert_extra_fields()[source]

Extra fields to take into account in plan_revert_single().

Singled out for easy subclassing, e.g., by the wms-quantity Blok.

Mandatory methods of Operation subclasses

after_insert()[source]
execute_planned()[source]
is_reversible()[source]

Moves are always reversible.

See the base class for what reversibility exactly means.

plan_revert_single(dt_execution, follows=())[source]

Model.Wms.Operation.Unpack

This Operation Model inherits from Mixin.WmsSingleInput

class anyblok_wms_base.core.operation.unpack.Unpack[source]

Unpacking some goods, creating new Goods and Avatar records.

This is a destructive Operation, in the usual mild sense: once it’s done, the input Goods Avatars is in the past state, and their underlying Goods have no new Avatars. It is meant to be reversible through appropriate Pack / Assembly Operations, which are not implemented yet at the time being.

Which Goods will get created and which Properties they will bear is specified in the unpack behaviour of the Type of the Goods being unpacked, together with their contents optional Properties. See get_outcome_specs() and forward_props() for details about these and how to achieve the wished functionality.

Unpacks happen in place: the newly created Avatar appear in the location where the input was. It is thus the caller’s responsibility to prepend moves to unpacking areas, and/or append moves to final destinations.

Fields and their semantics

id = <anyblok.column.Integer object>

Specific members

forward_props(spec, outcome)[source]

Handle the properties for a given outcome (Goods record)

This is actually a bit more that just forwarding.

Parameters:
  • spec (dict) – the relevant specification for this outcome, as produced by get_outcome_specs() (see below for the contents).
  • outcome – the just created Goods instance

Specification contents

  • properties:

    A direct mapping of properties to set on the outcome. These have the lowest precedence, meaning that they will be overridden by properties forwarded from self.input.

    Also, if spec has the local_goods_id key, properties is ignored. The rationale for this is that normally, there are no present or future Avatar for these Goods, and therefore the Properties of outcome should not have diverged from the contents of properties since the spec (which must itself not come from the behaviour, but instead from contents) has been created (typically by an Assembly).

  • required_properties:

    list (or iterable) of properties that are required on self.input. If one is missing, then OperationInputsError gets raised. forward_properties.

  • forward_properties:

    list (or iterable) of properties to copy if present from self.input to outcome.

Required properties aren’t automatically forwarded, so that it’s possible to require one for checking purposes without polluting the Properties of outcome. To forward and require a property, it has thus to be in both lists.

get_outcome_specs()[source]

Produce a complete specification for outcomes and their properties.

In what follows “the behaviour” means the value associated with the unpack key in the Goods Type behaviours.

Unless uniform_outcomes is set to True in the behaviour, the outcomes of the Unpack are obtained by merging those defined in the behaviour (under the outcomes key) and in the packs (self.input) contents Property.

This accomodates various use cases:

  • fixed outcomes:
    a 6-pack of orange juice bottles gets unpacked as 6 bottles
  • fully variable outcomes:
    a parcel with described contents
  • variable outcomes:
    a packaging with parts always present and some varying.

The properties on outcomes are set from those of self.input according to the forward_properties and required_properties of the outcomes, unless again if uniform_outcomes is set to True, in which case the properties of the packs (self.input) aren’t even read, but simply cloned (referenced again) in the outcomes. This should be better for performance in high volume operation. The same can be achieved on a given outcome by specifying the special 'clone' value for forward_properties.

Otherwise, the forward_properties and required_properties unpack behaviour from the Goods Type of the packs (self.input) are merged with those of the outcomes, so that, for instance forward_properties have three key/value sources:

  • at toplevel of the behaviour (uniform_outcomes=True)
  • in each outcome of the behaviour (outcomes key)
  • in each outcome of the Goods record (contents property)

Here’s a use-case: imagine the some purchase order reference is tracked as property po_ref (could be important for accounting).

A Goods Type representing an incoming package holding various Goods could specify that po_ref must be forwarded upon Unpack in all cases. For instance, a Goods record with that type could then specify that its outcomes are a phone with a given color property (to be forwarded upon Unpack) and a power adapter (whose colour is not tracked). Both the phone and the power adapter would get the po_ref forwarded, with no need to specify it on each in the incoming pack properties.

TODO DOC move a lot to global doc

create_unpacked_goods(fields, spec)[source]

Create Goods record according to given specification.

This singled out method is meant for easy subclassing (see, e.g, in wms-quantity Blok).

Parameters:
  • fields – pre-baked fields, prepared by the base class. In the current implementation, they are fully derived from spec, hence one may think of them as redundant, but the point is that they are outside the responsibility of this method.
  • spec – specification for these Goods, should be used minimally in subclasses, typically for quantity related adjustments. Also, if the special local_goods_ids is provided, this method should attempt to reuse the Goods record with that id (interplay with quantity might depend on the implementation).
Returns:

the list of created Goods records. In wms-core, there will be as many as the wished quantity, but in wms-quantity, this maybe a single record bearing the total quantity.

Overridden methods of Operation

classmethod check_create_conditions(state, dt_execution, inputs=None, quantity=None, **kwargs)[source]
cancel_single()[source]

Remove the newly created Goods, not only their Avatars.

Mandatory methods of Operation subclasses

after_insert()[source]
execute_planned()[source]

Model.Wms.Operation.Assembly

class anyblok_wms_base.core.operation.assembly.Assembly[source]

Assembly/Pack Operation.

This operation covers simple packing and assembly needs : those for which a single outcome is produced from the inputs, which must also be in the same Location.

The behaviour is specified on the outcome's Goods Type (see Assembly specification); it amounts to describe the expected inputs, and how to build the Properties of the outcome (see outcome_properties()). All Property related parameters in the specification are bound to the state to be reached or passed through.

A given Type can be assembled in different ways: the Assembly specification is chosen within the assembly Type behaviour according to the value of the name field.

Specific hooks are available for use-cases that aren’t covered by the specification format (example: to forward Properties with non uniform values from the inputs to the outcome). The name is the main dispatch key for these hooks, which don’t depend on the outcome's Good Type.

Fields and their semantics

id = <anyblok.column.Integer object>
outcome_type = <anyblok.relationship.Many2One object>

The Goods Type to produce.

name = <anyblok.column.Text object>

The name of the assembly, to be looked up in behaviour.

This field has a default value to accomodate the common case where there’s only one assembly for the given outcome_type.

Note

the default value is not enforced before flush, this can prove out to be really inconvenient for downstream code. TODO apply the default value in check_create_conditions() for convenience ?

parameters = <anyblok_postgres.column.Jsonb object>

Extra parameters specific to this instance.

This dict is merged with the parameters from the outcome_type behaviour to build the final specification.

match = <anyblok_postgres.column.Jsonb object>

Field use to store the result of inputs matching

Assembly Operations match their actual inputs (set at creation) with the inputs part of specification. This field is used to store the result, so that it’s available for further logic (for instance in the property setting hooks).

This field’s value is either None (before matching) or a list of lists: for each of the inputs specification, respecting ordering, the list of ids of the matching Avatars.

Specific members

specification

The Assembly specification

The Assembly specification is merged from two sources:

  • within the assembly part of the behaviour field of outcome_type, the subdict associated with name;
  • optionally, the instance specific parameters.

Here’s an example, for an Assembly whose name is 'soldering', also displaying most standard parameters. Individual aspects of these parameters are discussed in detail afterwards, as well as the merging logic.

On the outcome_type:

behaviours = {
   …
   'assembly': {
       'soldering': {
           'outcome_properties': {
               'planned': {'built_here': ['const', True]},
               'started': {'spam': ['const', 'eggs']},
               'done': {'serial': ['sequence', 'SOLDERINGS']},
           },
           'inputs': [
               {'type': 'GT1',
                'quantity': 1,
                'properties': {
                   'planned': {
                     'required': ['x'],
                   },
                   'started': {
                     'required': ['foo'],
                     'required_values': {'x': True},
                     'requirements': 'match',  # default is 'check'
                   },
                   'done': {
                     'forward': ['foo', 'bar'],
                     'requirements': 'check',
                   }
                },
               {'type': 'GT2',
                'quantity': 2
                },
               {'type': 'GT3',
                'quantity': 1,
                }
           ],
           'inputs_spec_type': {
               'planned': 'check',  # default is 'match'
               'started': 'match',  # default is 'check' for
                                    # 'started' and 'done' states
            },
           'for_contents': ['all', 'descriptions'],
           'allow_extra_inputs': True,
           'inputs_properties': {
               'planned': {
                  'required': …
                  'required_values': …
                  'forward': …
               },
               'started': …
               'done': …
           }
       }
       …
    }
}

On the Assembly instance:

parameters = {
    'outcome_properties': {
        'started': {'life': ['const', 'brian']}
    },
    'inputs': [
       {},
       {'code': 'ABC'},
       {'id': 1234},
    ]
    'inputs_properties': {
               'planned': {
                  'forward': ['foo', 'bar'],
               },
    },
}

Note

Non standard parameters can be specified, for use in Specific hooks.

Inputs

The inputs part of the specification is primarily a list of expected inputs, with various criteria (Goods Type, quantity, Goods code and Properties).

Besides requiring them in the first place, these criteria are also used to qualify (match) the inputs (note that Operation inputs are unordered in general, while this inputs parameter is). This spares the calling code the need to keep track of that qualification after selecting the goods in the first place. The result of that matching is stored in the match field, is kept for later Assembly state changes and can be used by application code, e.g., for operator display purposes.

Assemblies can also have extra inputs, according to the value of the allow_extra_inputs boolean parameter. This is especially useful for generic packing scenarios.

Having both specified and extra inputs is supported (imagine packing client parcels with specified wrapping, a greetings card plus variable contents).

The type criterion applies the Goods Type hierarchy, hence it’s possible to create a generic packing Assembly for a whole family of Goods Types (e.g., adult trekking shoes).

Similarly, all Property requirements take the properties inherited from the Goods Types into account.

Global Property specifications

The Assembly specification can have the following key/value pairs:

  • outcome_properties:
    a dict whose keys are Assembly states, and values are dicts of Properties to set on the outcome; the values are pairs (TYPE, EXPRESSION), evaluated by passing as positional arguments to eval_typed_expr().
  • inputs_properties:
    a dict whose keys are Assembly states, and values are themselves dicts with key/values:
    • required:
      list of properties that must be present on all inputs while reaching or passing through the given Assembly state, whatever their values
    • required_values:
      dict of Property key/value pairs that all inputs must bear while reaching or passing through the given Assembly state.
    • forward:
      list of properties to forward to the outcome while reaching or passing through the given Assembly state.

Per input Property checking, matching and forwarding

The same parameters as in inputs_properties can also be specified inside each dict that form the inputs list of the Assembly specification), as the properties sub parameter.

In that case, the Property requirements are used either as matching criteria on the inputs, or as a check on already matched Goods, according to the value of the inputs_spec_type parameter (default is 'match' in the planned Assembly state, and 'check' in the other states).

Example:

'inputs_spec_type': {
    'started': 'match',  # default is 'check' for
                         # 'started' and 'done' states
},
'inputs': [
    {'type': 'GT1',
     'quantity': 1,
     'properties': {
         'planned': {'required': ['x']},
         'started': {
             'required_values': {'x': True},
         },
         'done': {
             'forward': ['foo', 'bar'],
         },
    …
]

During matching, per input specifications are applied in order, but remember that the ordering of self.inputs itself is to be considered random.

In case inputs_spec_type is 'check', the checking is done on the Goods matched by previous states, thus avoiding a potentially costly rematching. In the above example, matching will be performed in the 'planned' and 'started' states, but a simple check will be done if going from the started to the done state.

It is therefore possible to plan an Assembly with partial information about its inputs (waiting for some Observation, or a previous Assembly to be done), and to refine that information, which can be displayed to operators, or have consequences on the Properties of the outcome, at each state change. In many cases, rematching the inputs for all state changes is unnecessary. That’s why, to avoid paying the computational cost three times, the default value is 'check' for the done and started states.

The result of matching is stored in the match field.

In all cases, if a given Property is to be forwarded from several inputs to the outcome and its values on these inputs aren’t equal, AssemblyPropertyConflict will be raised.

Passing through states

Following the general expectations about states of Operations, if an Assembly is created directly in the done state, it will apply the outcome_properties for the planned, started and done states. Also, the matching and checks of input Properties for the planned, started and done state will be performed, in that order.

In other words, it behaves exactly as if it had been first planned, then started, and finally executed.

Similarly, if a planned Assembly is executed (without being started first), then outcome Properties, matches and checks related to the started state are performed before those of the done state.

for_contents: building the contents Property

The outcome of the Assembly bears the special contents property, also used by Operation.Unpack.

This makes the reversal of Assemblies by Unpacks possible (with care in the behaviour specifications), and also can be used by applicative code to use information about the inputs even after the Assembly is done.

The building of the contents Property is controlled by the for_contents parameter, which itself is either None or a pair of strings, whose first element indicates which inputs to list, and the second how to list them.

The default value of for_contents is DEFAULT_FOR_CONTENTS.

If for_contents is None, no contents Property will be set on the outcome. Use this if it’s unnecessary pollution, for instance if it is custom set by specific hooks anyway, or if no Unpack for disassembly is ever to be wished.

for_contents: possible values of first element:

  • 'all':
    all inputs will be listed
  • 'extra':
    only the actual inputs that aren’t specified in the behaviour will be listed. This is useful in cases where the Unpack behaviour already takes the specified ones into account. Hence, the variable parts of Assembly and Unpack are consistent.

for_contents: possible values of second element:

  • 'descriptions':
    include Goods’ Types, those Properties that aren’t recoverable by an Unpack from the Assembly outcome, together with appropriate forward_properties for those who are (TODO except those that come from a global forward in the Assembly specification)
  • 'records':
    same as descriptions, but also includes the record ids, so that an Unpack following the Assembly would not give rise to new Goods records, but would reuse the existing ones, hence keep the promise that the Goods records are meant to track the “sameness” of the physical objects.

Merging logic

All sub parameters are merged according to the expected type. For instance, required and forward in the various Property parameters are merged as a set.

As displayed in the example above, if there’s an inputs part in parameters, it must be made of exactly the same number of dicts as within the outcome_type behaviour. More precisely, these lists are merged using the zip() Python builtin, which results in a truncation to the shortest. Of course, not having an inputs part in parameters does not result in empty inputs.

See also

SPEC_LIST_MERGE and dict_merge.

Specific hooks

While already powerful, the Property manipulations described above are not expected to fit all situations. This is obviously true for the rule forbidding the forwarding of values that aren’t equal for all relevant inputs: in some use cases, one would want to take the minimum of theses values, sum them, keep them as a list, or all of these at once… On the other hand, the specification is already complicated enough as it is.

Therefore, the core will stick to these still relatively simple primitives, but will also provide the means to perform custom logic, through assembly-specific hooks

DEFAULT_FOR_CONTENTS = ('extra', 'records')

Default value of the for_contents part of specification.

See outcome_properties() for the meaning of the values.

SPEC_LIST_MERGE = {'inputs': ('zip', {'*': {'properties': {'*': {'required': ('set', None), 'forward': ('set', None)}}}}), 'inputs_properties': {'*': {'required': ('set', None), 'forward': ('set', None)}}}
outcome_properties(state, for_creation=False)[source]

Method responsible for properties on the outcome.

For the given state that is been reached, this method returns a dict of Properties to apply on the outcome.

Parameters:
  • state – The Assembly state that we are reaching.
  • for_creation (bool) – if True, means that this is part of the creation process, i.e, there’s no previous state.
Return type:

Model.Wms.Goods.Properties

Raises:

AssemblyInputNotMatched if one of the input specifications is not matched by self.inputs, AssemblyPropertyConflict in case of conflicting values for the outcome.

The specific hook gets called at the very end of the process, giving it higher precedence than any other source of Properties.

eval_typed_expr(etype, expr)[source]

Evaluate a typed expression.

Parameters:
  • expr – the expression to evaluate
  • etype – the type or expr.

Possible values for etype

  • 'const':
    expr is considered to be a constant and gets returned directly. Any Python value that is JSON serializable is admissible.
  • 'sequence':
    expr must be the code of a Model.System.Sequence instance. The return value is the formatted value of that sequence, after incrementation.
specific_outcome_properties(assembled_props, state, for_creation=False)[source]

Hook for per-name specific update of Properties on outcome.

At the time of Operation creation or execution, this calls a specific method whose name is derived from the name field, by this format, if that method exists.

Applicative code is meant to override the present Model to provide the specific method. The signature to implement is identical to the present one:

Parameters:
  • state – The Assembly state that we are reaching.
  • assembled_props (dict) – a dict of already prepared Properties for this state.
  • for_creation (bool) – if True, means that this is part of the creation process, i.e, there’s no previous state.
Returns:

the properties to set or update

Return type:

any iterable that can be passed to dict.update().

props_hook_fmt = 'outcome_properties_{name}'

Mandatory methods of Operation subclasses

classmethod check_create_conditions(state, dt_execution, inputs=None, outcome_type=None, name=None, **kwargs)[source]
after_insert()[source]
execute_planned()[source]

Check or rematch inputs, update properties and states.