This Operation Model inherits from Mixin.WmsSingleInput
anyblok_wms_base.core.operation.move.
Move
[source]¶A stock move
id
= <anyblok.column.Integer object>¶destination
= <anyblok.relationship.Many2One object>¶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.
is_reversible
()[source]¶Moves are always reversible.
See the base class
for what
reversibility exactly means.
This Operation Model inherits from Mixin.WmsSingleInput
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.
id
= <anyblok.column.Integer object>¶forward_props
(spec, outcome)[source]¶Handle the properties for a given outcome (Goods record)
This is actually a bit more that just forwarding.
Parameters: |
|
---|
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:
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:
uniform_outcomes=True
)outcomes
key)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: |
|
---|---|
Returns: | the list of created Goods records. In |
check_create_conditions
(state, dt_execution, inputs=None, quantity=None, **kwargs)[source]¶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
.
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.
specification
¶The Assembly specification
The Assembly specification is merged from two sources:
assembly
part of the behaviour field of
outcome_type
, the subdict associated with name
;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
:(TYPE, EXPRESSION)
, evaluated by passing as
positional arguments to eval_typed_expr()
.inputs_properties
: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'
:'extra'
:for_contents: possible values of second element:
'descriptions'
:forward_properties
for those who are (TODO except those that
come from a global forward
in the Assembly specification)'records'
: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
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: |
|
---|---|
Return type: |
|
Raises: |
|
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: |
|
---|
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: |
|
---|---|
Returns: | the properties to set or update |
Return type: | any iterable that can be passed to |
props_hook_fmt
= 'outcome_properties_{name}'¶