单据类型

Single DocType is a special type of DocType that stores only one record. It is useful for storing settings or configuration values that are global to the application. The data is stored in a single row in the database, and the form is always available for editing.

Library Settings Controller

Now, let’s write code to use the Library Settings in our validations. We will
update the Library Transaction validation to check the loan period and
maximum number of issued articles.

library_transaction.py

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)

We used the tabSingles method to get the Library Settings record. Since it
is a Single DocType, we can directly access its fields. We also used the
frappe.db.get_single_value method to count the number of issued articles for the member.

Library Membership Validation Update

We also need to update the Library Membership validation to use the Library
Settings for the loan period.

library_membership.py

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")

Now, when you create a Library Membership, the loan period will be automatically
set based on the Library Settings.

Summary

In this tutorial, we learned about different types of doctypes in the framework:

  • Master and Transactional DocTypes
  • Submittable DocTypes
  • Single DocTypes

We also learned how to use Link fields to connect records, fetch values from
linked records, and write validations for our doctypes.

单一文档类型类似于其他框架中的单例记录。它不会
创建新的数据库表。相反,所有单一值都存储在一个名为
tabSingles 的表中。它通常用于存储全局设置。

库设置的验证

让我们对“库会员”进行修改,使得“截止日期”自动
根据“借阅期限”和“起始日期”计算得出。

library_membership.py

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)

我们使用了 frappe.db.get_single_value 方法来获取
“库设置”文档类型中的 loan_period 值。

现在,让我们对“库事务”进行修改,使得当文章被
“借出”时,它会检查是否达到了最大限制。

library_transaction.py

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")

我们添加了一个 validate_maximum_limit 方法,并使用 frappe.db.count 来统计
该会员进行的交易次数。

至此,我们已经涵盖了文档类型创建的基础知识和文档类型的种类。
我们还为各种文档类型编写了业务逻辑。

干得好,坚持到了这里。让我们继续前进。

下一步:表单脚本