Source code for anyblok_wms_base.inventory.order
# -*- coding: utf-8 -*-
# This file is a part of the AnyBlok / WMS Base project
#
# Copyright (C) 2018 Georges Racinet <gracinet@anybox.fr>
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file,You can
# obtain one at http://mozilla.org/MPL/2.0/.
from anyblok import Declarations
from anyblok.column import Integer
from anyblok_postgres.column import Jsonb
from .exceptions import (NodeStateError,
)
register = Declarations.register
Model = Declarations.Model
[docs]@register(Model.Wms)
class Inventory:
"""This model represents the decision of making an Inventory.
It expresses a global specification for the inventory process to be made
as well as human level additional information.
Applicative code is welcomed and actually supposed to override this to
add more columns as needed (dates, creator, reason, comments...)
Instances of :class:`Wms.Inventory <Inventory>` are linked to a tree
of processing :class:`Nodes <anyblok_wms_base.inventory.node.Node>`,
which is reachable with the convenience :attr:`root` attribute.
TODO structural Properties to use throughout the whole hierarchy
for Physical Object identification
This tree is designed for distribution of the assessment and reconciliation
work, but it's possible to compute all reconciliations and apply them on
an Inventory for testing purposes as follows (assuming that all related
:class:`Nodes <.node.Node>` are in the ``full`` state)::
inventory.root.recurse_compute_push_actions()
inventory.reconcile_all()
"""
id = Integer(label="Identifier", primary_key=True)
"""Primary key."""
excluded_types = Jsonb()
"""List of Physobj.Type codes to be excluded.
This is not the smartest way of excluding stuff, but it's good enough
for time being.
The primary use-case is to exclude some/most of the container types
from inventories, which could also be done by excluding all container types
with a recursive query involving behaviours, but that's a performance hit
for something that can be done by simply excluding a few types.
"""
considered_types = Jsonb()
"""List of ``Physobj.Type`` codes to be considered.
Similarly to :attr:`excluded_types`, this is good enough and can be
later be improved by adding a flag to make it recursive.
"""
@property
def root(self):
"""Root Node of the Inventory."""
return (self.registry.Wms.Inventory.Node.query()
.filter_by(inventory=self, parent=None)
.one())
[docs] @classmethod
def create(cls, location, **fields):
"""Insert a new Inventory together with its root Node.
:return: the new Inventory
"""
Node = cls.registry.Wms.Inventory.Node
inventory = cls.insert(**fields)
Node.insert(inventory=inventory, location=location)
return inventory
[docs] def reconcile_all(self):
"""Convenience method to apply all Actions linked to this Inventory.
This is a straightforward yet non scalable implementation of
the final reconciliation (see below). Don't use it on large
installations.
To run it, it is required that the :attr:`root` Node has reached
the ``pushed`` state.
:raises: NodeStateError if :attr:`root` Node is not ready.
This method does everything in one shot, therefore leading to huge
database transactions on full inventories of large installations.
For large inventories, a more progressive way of doing is required,
perhaps Node per Node plus batching for each Node.
Nodes wouldn't have to be taken
in order, but care must be taken while updating their state
to 'reconciled' in out of order executions with several batches per
Node.
"""
root = self.root
if root.state != 'pushed':
raise NodeStateError(root, "This root {node} has not "
"reached the 'pushed' state "
"(currently at {state!r})")
Node = self.Node
Action = self.Action
for action in (Action.query()
.join(Node, Node.id == Action.node_id)
.filter(Node.inventory == self)
.all()):
action.apply()
(Node.query()
.filter_by(inventory=self)
.update(dict(state='reconciled'), synchronize_session='fetch'))