Documentation Frappe Framework

Frappe Framework

Types de DocType

Septième étape du tutoriel : création des DocTypes Library Membership, Library Transaction et Library Settings pour illustrer les champs Link, les documents soumissibles et les DocTypes Single.

8 min de lectureMis à jour le Source : docs.frappe.io

Objectif

Découvrons les différents types de DocType du framework en créant de nouveaux DocTypes pour notre app de bibliothèque.

Library Membership

Créons un nouveau DocType : Library Membership (adhésion). Il comporte les champs suivants :

Libellé du champTypeRemarque
Library MemberLinkObligatoire (Mandatory)
Full NameDataLecture seule (Read Only)
From DateDate
To DateDate
PaidCheck

Ce DocType a l’option Is Submittable activée. Son nommage (Naming) est défini sur LMS.##### et il est réservé au rôle Librarian. Enfin, dans la section View Settings, le champ Title Field doit valoir full_name. La documentation officielle illustre cette configuration par une capture d’écran.

Le champ de lien (Link) Library Member s’apparente à une clé étrangère dans d’autres frameworks : il permet de lier la valeur à un enregistrement d’un autre DocType. Ici, il pointe vers un enregistrement du DocType Library Member.

Le champ Full Name est en lecture seule : sa valeur est automatiquement récupérée (fetched from) depuis le champ full_name de l’enregistrement lié dans Library Member.

Ouvrez maintenant la liste Library Membership et créez un nouveau document. Le champ Library Member se présente sous forme de liste déroulante proposant les enregistrements existants. Sélectionnez un membre : le Full Name est récupéré automatiquement.

DocTypes liés

Les DocTypes liés (Linked DocTypes) sont des DocTypes référencés dans d’autres DocTypes par des champs Link. Tous les DocTypes peuvent être liés. On peut classer les DocTypes en deux grandes familles selon le type de données qu’ils stockent : données de référence (Master) et données transactionnelles (Transactional). Article et Library Member sont des données de référence, car ils représentent une entité (physique ou virtuelle). Library Membership stocke des données transactionnelles.

DocTypes soumissibles

Lorsque l’option Is Submittable est activée, le DocType devient soumissible (Submittable). Un document soumissible peut avoir trois états : Draft (brouillon), Submitted (soumis) et Cancelled (annulé).

  • À l’état Draft, le document se modifie comme n’importe quel document.
  • Une fois Submitted, aucun champ du document ne peut plus être modifié.
  • Un document soumis peut être Cancelled, ce qui le rend invalide.

Vous avez peut-être remarqué qu’un champ supplémentaire, Amended From, a été ajouté au DocType Library Membership. Il sert à suivre les amendements des documents : une fois annulé, un document ne peut qu’être amendé, c’est-à-dire dupliqué ; le document annulé est alors lié au nouveau document amendé via le champ Amended From.

Validation dans le contrôleur de Library Membership

Écrivons du code qui garantit, à la création d’une adhésion, que le membre n’a pas déjà une adhésion active.

library_membership.py

python
import frappe
from frappe.model.document import Document
from frappe.model.docstatus import DocStatus

class LibraryMembership(Document):
    # check before submitting this document
    def before_submit(self):
        exists = frappe.db.exists(
            "Library Membership",
            {
                "library_member": self.library_member,
                "docstatus": DocStatus.submitted(),
                # check if the membership's end date is later than this membership's start date
                "to_date": (">", self.from_date),
            },
        )
        if exists:
            frappe.throw("There is an active membership for this member")

La logique est placée dans la méthode before_submit, exécutée avant la soumission du document. La méthode frappe.db.exists vérifie s’il existe un enregistrement Library Membership correspondant aux filtres fournis. Si c’est le cas, frappe.throw interrompt l’exécution et affiche un message expliquant la raison à l’utilisateur.

Essayez de créer une adhésion dont la période chevauche une adhésion existante : une erreur s’affiche à la soumission du document.

Library Transaction

Créons un DocType pour enregistrer l’emprunt (Issue) ou le retour (Return) d’un article par un membre disposant d’une adhésion active.

Ce DocType s’appelle Library Transaction et comporte les champs suivants :

Libellé du champTypeRemarque
ArticleLinkLien vers Article
Library MemberLinkLien vers Library Member
TypeSelectDeux options : Issue et Return
DateDateDate de la transaction

Ce DocType est lui aussi soumissible.

Validation de la transaction

Lorsqu’un article est emprunté, il faut vérifier que le membre dispose d’une adhésion active et que l’article est disponible. Écrivons ces validations.

library_transaction.py

python
import frappe
from frappe.model.document import Document
from frappe.model.docstatus import DocStatus

class LibraryTransaction(Document):
    def before_submit(self):
        if self.type == "Issue":
            self.validate_issue()
            # set the article status to be Issued
            article = frappe.get_doc("Article", self.article)
            article.status = "Issued"
            article.save()

        elif self.type == "Return":
            self.validate_return()
            # set the article status to be Available
            article = frappe.get_doc("Article", self.article)
            article.status = "Available"
            article.save()

    def validate_issue(self):
        self.validate_membership()
        article = frappe.get_doc("Article", self.article)
        # article cannot be issued if it is already issued
        if article.status == "Issued":
            frappe.throw("Article is already issued by another member")

    def validate_return(self):
        article = frappe.get_doc("Article", self.article)
        # article cannot be returned if it is not issued first
        if article.status == "Available":
            frappe.throw("Article cannot be returned without being issued first")

    def validate_membership(self):
        # check if a valid membership exist for this library member
        valid_membership = frappe.db.exists(
            "Library Membership",
            {
                "library_member": self.library_member,
                "docstatus": DocStatus.submitted(),
                "from_date": ("<", self.date),
                "to_date": (">", self.date),
            },
        )
        if not valid_membership:
            frappe.throw("The member does not have a valid membership")

Le code est assez long, mais il se lit facilement ; les commentaires en ligne apportent des explications complémentaires.

Library Settings

Créons le dernier DocType de l’app : Library Settings. Il comporte les champs suivants :

Libellé du champRôle
Loan PeriodDéfinit la durée d’emprunt en nombre de jours
Maximum Number of Issued ArticlesLimite le nombre maximal d’articles qu’un même membre peut emprunter

Comme nous n’avons pas besoin de plusieurs enregistrements pour ces paramètres, nous activons Is Single sur ce DocType.

Après la création, cliquez sur Go to Library Settings pour ouvrir le formulaire et renseignez Loan Period et Maximum Number of Issued Articles.

DocTypes Single

Lorsque l’option Is Single est activée, le DocType devient un DocType Single. Il s’apparente aux enregistrements singleton d’autres frameworks : aucune nouvelle table n’est créée. Toutes les valeurs des DocTypes Single sont stockées dans une seule table, tabSingles. Ce type sert généralement à stocker des paramètres globaux.

Utiliser Library Settings dans les validations

Modifions Library Membership pour que To Date soit calculé automatiquement à partir de Loan Period et de From Date.

library_membership.py

python
from frappe.model.document import Document
from frappe.model.docstatus import DocStatus

import frappe

class LibraryMembership(Document):
    # check before submitting this document
    def before_submit(self):
        exists = frappe.db.exists(
            "Library Membership",
            {
                "library_member": self.library_member,
                "docstatus": DocStatus.submitted(),
                # check if the membership's end date is later than this membership's start date
                "to_date": (">", self.from_date),
            },
        )
        if exists:
            frappe.throw("There is an active membership for this member")

        # get loan period and compute to_date by adding loan_period to from_date
        loan_period = frappe.db.get_single_value("Library Settings", "loan_period")
        self.to_date = frappe.utils.add_days(self.from_date, loan_period or 30)

La méthode frappe.db.get_single_value récupère la valeur de loan_period dans le DocType Library Settings.

Modifions maintenant Library Transaction pour qu’à l’emprunt d’un article, la limite maximale soit vérifiée.

library_transaction.py

python
import frappe
from frappe.model.document import Document
from frappe.model.docstatus import DocStatus

class LibraryTransaction(Document):
    def before_submit(self):
        if self.type == "Issue":
            self.validate_issue()
            self.validate_maximum_limit()
            # set the article status to be Issued
            article = frappe.get_doc("Article", self.article)
            article.status = "Issued"
            article.save()

        elif self.type == "Return":
            self.validate_return()
            # set the article status to be Available
            article = frappe.get_doc("Article", self.article)
            article.status = "Available"
            article.save()

    def validate_issue(self):
        self.validate_membership()
        article = frappe.get_doc("Article", self.article)
        # article cannot be issued if it is already issued
        if article.status == "Issued":
            frappe.throw("Article is already issued by another member")

    def validate_return(self):
        article = frappe.get_doc("Article", self.article)
        # article cannot be returned if it is not issued first
        if article.status == "Available":
            frappe.throw("Article cannot be returned without being issued first")

    def validate_maximum_limit(self):
        max_articles = frappe.db.get_single_value("Library Settings", "max_articles")
        count = frappe.db.count(
            "Library Transaction",
            {
                "library_member": self.library_member,
                "type": "Issue",
                "docstatus": DocStatus.submitted(),
            },
        )
        if count >= max_articles:
            frappe.throw("Maximum limit reached for issuing articles")

    def validate_membership(self):
        # check if a valid membership exist for this library member
        valid_membership = frappe.db.exists(
            "Library Membership",
            {
                "library_member": self.library_member,
                "docstatus": DocStatus.submitted(),
                "from_date": ("<", self.date),
                "to_date": (">", self.date),
            },
        )
        if not valid_membership:
            frappe.throw("The member does not have a valid membership")

Nous avons ajouté une méthode validate_maximum_limit qui utilise frappe.db.count pour compter le nombre de transactions effectuées par le membre.

Nous avons ainsi couvert les bases de la création de DocTypes et leurs différents types, et écrit la logique métier de plusieurs DocTypes.

Bravo d’être arrivé jusqu’ici. Continuons !

Étape suivante : Scripts de formulaire.

Pages liées