AnyBlok / WMS Base

Un moteur d'applications de logistique avec Python 3, SQLAlchemy, PostgreSQL et AnyBlok.

Présentation disponible en ligne

https://github.com/gracinet/awb-pyconfr-2018/25mn.rst

Georges Racinet

Cas d'utilisation

État du projet

But de la présentation

J'assume…

Cas d'utilisation

Points communs

Avant passer de passer à du concret, un petit mot sur motivation.

Motivation / objectifs

Le scénario

Objets physiques : le modèle PhysObj

On récupère un type, puis les objets physiques de ce type

Prendre son temps sur cet écran.

  • rappeler Anyblok, modèle

Insister sur première spécificité (une ligne par objet physique):

  • pas de quantité
  • pas de système d'unités
>>> PhysObj = registry.Wms.PhysObj
>>> livre_type = PhysObj.Type.query().filter_by(code='GR-DUST-WIND-VOL2').one())
>>> exemplaires = PhysObj.query().filter_by(type=livre_type).all()
>>> exemplaires
[Wms.PhysObj(id=18, type=Wms.PhysObj.Type(id=7, code='GR-DUST-WIND-VOL2')),
Wms.PhysObj(id=19, type=Wms.PhysObj.Type(id=7, code='GR-DUST-WIND-VOL2')),
Wms.PhysObj(id=20, type=Wms.PhysObj.Type(id=7, code='GR-DUST-WIND-VOL2')),
Wms.PhysObj(id=21, type=Wms.PhysObj.Type(id=7, code='GR-DUST-WIND-VOL2')),
Wms.PhysObj(id=22, type=Wms.PhysObj.Type(id=7, code='GR-DUST-WIND-VOL2'))]

PhysObj : les Propriétés

En plus du type, on dispose d'un système de propriétés flexibles.

>>> exemplaires[0]
Wms.PhysObj(id=18, type=Wms.PhysObj.Type(id=7, code='GR-DUST-WIND-VOL2')
>>> exemplaires[0].merged_properties()
{'lot': '12A345'}

>>> exemplaires[0].set_property('expo', True)
>>> exemplaires[0].get_property('expo')
True

Sous le capot: un champ JSONB, ou des colonnes séparées

PhysObj : retour sur les Types

Si c'est différent (à manipuler) ce n'est pas la même chose !

Donc un carton de 50, c'est un autre type que pour 50 exemplaires:

>>> carton = PhysObj.Type.query().filter_by(code='GR-DUST-WIND-VOL1/PALETTE').one()
>>> PhysObj.query().filter_by(type=carton).count()
0

Et une palette de 80 cartons, c'est encore autre chose que 80 cartons:

>>> palette = PhysObj.Type.query().filter_by(code='GR-DUST-WIND-VOL1/PALETTE').one()
>>> PhysObj.query().filter_by(type=palette).all()
[Wms.PhysObj(id=20, type=Wms.PhysObj.Type(id=6, code='GR-DUST-WIND-VOL1/PALETTE'))]

PhysObj.Avatar : où et quand

Les avatars encodent la présence d'un objet physique quelque part pour un certain laps de temps.

>>> Avatar = PhysObj.Avatar

>>> avatars = Avatar.query().filter_by(obj=exemplaires[0]).order_by(Avatar.dt_from).all()
>>> [(av.state, av.location.code, str(av.dt_from)) for av in avatars]

[('past', 'QUAI ENTRÉE', '2018-10-06 01:00:40.366405+02:00'),
('past', 'CASIER3', '2018-10-06 01:00:40.397054+02:00'),
('present', 'EMBALLAGE', '2018-10-06 01:00:40.416139+02:00'),
('future', 'QUAI SORTIE', '2018-10-07 13:00:40.416139+02:00')]

Les emplacements sont des objets physiques !

>>> avatars[0].location
Wms.PhysObj(id=2, code='QUAI ENTRÉE', type=Wms.PhysObj.Type(id=1, code='EMPLACEMENT FIXE'))

PhysObj.Avatar : où et quand

Motivation de la séparation entre PhysObj et PhysObj.Avatar :

  • hygiène de base de données
  • réservation

PhysObj.Avatar : où et quand

Motivation de la séparation entre PhysObj et PhysObj.Avatar :

  • hygiène de base de données
  • réservation

Opérations

>>> op = avatars[-1].reason
>>> op
Model.Wms.Operation.Move(id=17, state='planned',
                         input=Wms.PhysObj.Avatar(...),
                         destination=Wms.PhysObj(id=4, code='QUAI SORTIE',  ...)
>>> op.execute()
>>> avatars[-1].state
'present'

et pour finir, expédions !

>>> registry.Wms.Operation.Departure.create(input=avatars[-1], state='done')
>>> avatars[-1].state
'past'

Pas de modèle Wms.Location ?

Une certaine indirection…

AnyBlok / Wms Base fournit ce qu'il faut pour les quantités de stocks.

Avantages

Opérations : cycle de vie

Opérations : cycle de vie

Opérations disponibles

Composants d'Anyblok / Wms Base

Jusqu'ici, c'était le Blok wms-core. Il y a aussi :

Développements futurs

https://anyblok-wms-base.readthedocs.io/en/latest/improvements.html

Beaucoup de choses intéressantes restent à faire :

But de la présentation

Je reviens sur l'objectif initial…

À vous pour les questions et suggestions !

Complément : déballage

>>> palette
Wms.PhysObj.Type(id=7, code='GR-DUST-WIND-VOL1/PALETTE')
>>> palette_av = Avatar.query().join(Avatar.obj).filter_by(type=palette).one()
>>> palette_av.state, palette_av.location.code
('present', 'SALLE1')
>>> unpack = registry.Wms.Operation.Unpack.create(input=palette_av, state='done')
>>> len(unpack.outcomes)
81

>>> set((avatar.state, avatar.obj.type.code, avatar.location.code)
...     for avatar in unpack.outcomes)
{('present', 'GR-DUST-WIND-VOL1/CARTON', 'SALLE1'),
('present', 'PALETTE SUPPORT', 'SALLE1')}

Déballage (déclaration)

>>> palette
Wms.PhysObj.Type(id=7, code='GR-DUST-WIND-VOL1/PALETTE')
>>> palette.behaviours['unpack']
{'outcomes': [{'forward_properties': ['lot'],
               'quantity': 80,
               'required_properties': [],
               'type': 'GR-DUST-WIND-VOL1/CARTON'},
              {'forward_properties': [],
              'quantity': 1,
              'required_properties': [],
              'type': 'PALETTE SUPPORT'}]}}
SpaceForward
Right, Down, Page DownNext slide
Left, Up, Page UpPrevious slide
GGo to slide number
POpen presenter console
HToggle this help