Voilà donc environ une semaine que j'ai produit cette version 0.7.
Comme le numéro l'indique, on est toujours dans le pré-alpha, et je dois dire qu'à ce stade, c'est un peu pour le principe que l'on produit des versions officielles, car tous les développeurs que je connais utilisent directement les dépôts Git. Mais sortir des versions aide néanmoins à rythmer les choses, et nous prépare pour le moment où elles serviront vraiment de référence.
Cela dit, beaucoup de choses se sont produites durant ce cycle de développement, prévues d'avance ou non dans la feuille de route.
Un billet forcément en forme de catalogue, mais c'est l'occasion aussi de commenter, d'expliquer la démarche.
Applications et cas d'utilisation
Un cas B2C avec assemblages personnalisés
Comme prévu, le développement d'une application métier commerciale a bien commencé. Je préfère ne pas donner trop de détails à ce stade, mais disons qu'il s'agit d'une petite volumétrie pour du B2C, avec des besoins d'assemblage très spécifiques en plus des opérations habituelles que sont réceptions, rangements et envois.
Pas de surprise ici, c'est l'intérêt de ce client qui a mis Anyblok / WMS Base sur les rails, je suis content que le développement aval soit en cours.
Gestion de parc de matériel scénique
Aux antipodes de cela, j'ai commencé à utiliser Anyblok / WMS Base pour inventorier et suivre le matériel de concert de mon association de musiciens. L'objectif est d'identifier précisément et de tracer les différents éléments, en particulier les câbles, qui sont nombreux d'un type donné, même pour une poignée de groupes de rock. Ceux qui se sont trouvés à monter un concert avec 20 câbles indiscernables dont deux défaillants, occasionnant une perte de temps conséquente et qui ont dû revivre cette experience plusieus fois car les câbles en question n'ont pas été mis à part au moment du rangement me comprendront…
C'est bien entendu à l'échelle de cette petite association totalement déraisonnable, surtout sans interface utilisateur, mais je le fais pour la synergie, et pour tout dire, cela a d'ores et déjà fait sortir des cas d'utilisation intéressants dans l'absolu. De plus, qui sait si un jour on n'aura pas une application 100% libre pour les prestataires son & lumière, avec fiches de sortie, historique d'entretien, etc. ?
En tout cas je compte bien publier les quelques structures de données et utilitaires que j'ai produits à l'occasion – à mon retour de festival. En attendant, cela fait une source d'exemples de plus !
Les nouveautés
Propriétés et hiérarchie des types de marchandises
Jusqu'ici, j'avais tendance à penser que les propriétés des marchandises (Goods) servaient à encoder la variabilité qui n'est pas déjà exprimée dans leur Type, sans trop traiter la question de comment les applications pourraient justement s'y prendre pour implémenter ce qui, dans leur logique métier, dépendrait des Types. Certes il y avait déjà les behaviours, utilisées jusqu'ici majoritairement pour configurer les opérations, mais il manquait une couche de souplesse supplémentaire.
Finalement, ce qui a été tenté, c'est d'ajouter en plus une notion de propriétés sur les Types eux-mêmes, avec dans l'idée que, contrairement aux behaviours, cela soit interchangeable avec les propriétés des marchandises elles-mêmes.
Donc à partir de la 0.7, on a des propriétés sur les Goods et leurs Types, avec une logique d'héritage qui fonctionne propriété par propriété.
Pour un exemple d'application concrète, imaginons que j'aie envie d'avoir une propriété « longueur » pour mes câbles de micro, mais j'en ai beaucoup qui font exactement 6 mètres. Je n'ai ni envie de dire que la longueur fait toujours partie du Type, car cela m'obligerait à faire un nouveau type pour chaque variante, ni envie de dire que c'est toujours en propriété sur l'enregistrement Goods lui-même, pour ne pas recopier des valeurs identiques tout le temps. Avec les propriétés sur le Type et leur héritage, je peux faire les deux.
De plus, c'est une autre nouveauté de 0.7, les Types forment maintenant une hiérarchie arborescente, là aussi avec héritage des propriétés. Voici un exemple complet:
>>> cable = Goods.Type.insert(code='CABLE') >>> cable_mic = Goods.Type.insert(code='CABLE/MIC', ... parent=cable, ... properties=dict(blindage=True)) >>> cm6 = Goods.Type.insert(code='CABLE/MIC/6', ... parent=cable_mic, ... properties=dict(longueur=6))
Créons donc deux câbles en utilisant ces deux Types (dans une vraie application, on ferait plutôt une Opération, par exemple une Arrival):
>>> g1 = Goods.insert(type=cable_mic) >>> g1.set_property('longueur', 4) >>> g2 = Goods.insert(type=cm6)
Grâce à l'API de haut niveau, on peut traiter la longueur de ces câbles de manière uniforme:
>>> [g.get_property('longueur') for g in (g1, g2)] [4, 6]
Et si l'on a besoin de savoir de manière uniforme si les câbles sont blindés ou non:
>>> [g.get_property('blindage') for g in (g1, g2)] [True, True]
Et pour finir:
>>> [g.has_type(cm6) for g in (g1, g2)] [False, True] >>> [g.has_type(cable_mic) for g in (g1, g2)] [True, True]
Sur le plan technique, tout ceci est implémenté avec des champs JSONB, et c'est le code Python qui s'occupe de l'héritage. Cela a le défaut, pour le moment non traité, de compliquer le requêtage (par exemple si je voulais lister tous les câbles de longueur supérieure à 3 mètres). On peut dire aussi que cela nous éloigne de la logique SQL, qui a ses avantages propres. Que l'on considère cela comme excitant ou un peu dangereux est une question de point de vue. On va de toute façon essayer de faire en sorte de pouvoir faire évoluer la représentation des données en base à l'avenir si besoin.
Et le rapport avec les behaviours dans tout cela ? Hé bien, je les imagine jouer un rôle plus abstrait, et devraient être cantonnées à ce que l'on veut vraiment indiquer au niveau du Type, sans jamais amender sur les Goods eux-mêmes. Par exemple, ce seraient elles qui pourraient indiquer que l'unité de longueur pour les câbles est le mètre.
Pour l'instant, les behaviours ne s'héritent pas, mais certaines opérations implémentent un héritage.
Emplacements et requêtage avancé
Voilà qui était sur la feuille de route !
Les requêtes fournies d'office pour les niveaux de stock sont maintenant capables d'exploiter l'arborescence, qui, elle, était présente dès le début, mais il y avait une question à trancher : tenir compte de manière strice de l'arborescence conduit à compter comme disponibles à la vente des articles qui sont en zone technique, ne pas tenir compte de l'aborescence la rend à peu près inutile.
Finalement ce qui a été fait, c'est un système d'étiquettes sur les emplacements, avec un héritage par défaut, mais par défaut seulement. Ainsi l'on peut avoir par exemple une salle étiquettée « pour la vente », avec des armoires de rangement qui hériteront de l'étiquette, mais aussi une zone servant à regrouper les articles endommagés, avec une étiquette « poubelle ».
Les quantités en option
Comme prévu aussi, les enregistrements de marchandises ne portent désormais plus de champ quantité, sauf si l'on installe le Blok wms-quantity Autrement dit, par défaut, le système ne traite les marchandises qu'à la pièce, mais rappelons que dans la logique d'Anyblok / WMS Base, on est censé faire un Type dédié par conditionnement et les relier par des opérations d'emballage/déballage. Si l'on reçoit des palettes entières de jus d'orange en cartons de douze, et que l'on redistribue les cartons entiers, nulle raison de créer un enregistrement pour chaque flacon !
En fait, le champ quantité n'est totalement nécessaire que dans les cas de marchandises en vrac (donc au poids ou à la longueur), et n'est que marginalement utile dans les autres cas : seulement si l'on manipule souvent plusieurs biens identiques à la fois, mais sans que ce soit en les regroupant dans un Type dédié comme dans l'exemple du carton de douze.
C'était un assez gros changement, bien que techniquement facile, que je suis bien content d'avoir fait avant d'avoir des applications en production.
Au passage, les tests pour l'intégration continue sont maintenant lancés par un script dédié, qui prend garde à additionner la couverture comme il faut.
Assembly, l'opération pour les emballages et fabrications simples
La version 0.7 apporte une nouvelle Opération, l'Assembly qui sert pour tous les cas où l'on prend plusieurs objets pour en faire un seul.
C'est en fait une machine à analyser, valider et trier les entrées (matières premières) par type et propriété, qui peut en particulier servir de support à une interface utilisateur, ou pour transmettre à une machine.
Différents cas sont prévus :
- assemblage avec matières premières fixées, par exemple une planche et quatre pieds pour faire une table ;
- assemblage totalement libre, typiquement pour emballer, si l'on ne suit pas le matériau d'emballage en stock ;
- assemblage partiellement fixé, par exemple pour un emballage avec carton identifié et accessoire inclus d'office dans tous les cas…
Les Assemblies sont capables de créer des propriétés aux différentes étapes du processus. L'exemple typique serait le numéro de série.
Un peu à la marge, en fait on peut utiliser des Assemblies avec une seule entrée, c'est un moyen dans ce cas d'exprimer une transformation structurelle, car l'on obtient une nouvelle ligne dans la table des Goods en sortie.
Pour ce qui est de la validation des entrées, les Assemblies utilisent les APIs de haut niveau pour les Types et les Propriétés. Ainsi, un Type enfant sera accepté si son parent est spécifié, et peu importe que la propriété vérifiée vienne de l'objet concret ou de son type.
De plus, au moment de la création d'une Assembly, on peut passer des paramètres supplémentaires pour affiner les choses, indiquer plus finement le rôle de chaque entrée, etc.
Ce qui est remis à plus tard
Le travail pour les communications avec d'autres systèmes (API HTTP, bus…) n'a pas commencé. Ce n'est pas vraiment une urgence pour le moment de toute façon.