انواع المستندات

لنتعلم عن الأنواع المختلفة لنوع المستند في الإطار من خلال إنشاء المزيد من أنواع المستندات.

عضوية المكتبة

لنقم بإنشاء نوع مستند آخر: Library Membership. سيكون له الحقول التالية:

  • Library Member (نوع Link، إلزامي Mandatory)
  • Full Name (نوع Data، للقراءة فقط Read Only)
  • From Date (نوع Date)
  • To Date (نوع Date)
  • Paid (نوع Check)

سيكون لديه Is Submittable مفعل. سيكون لديه Naming مضبوطًا على LMS.##### ومقيدًا بدور Librarian. أيضًا، يجب تعيين Title Field على full_name في قسم View Settings.

<اسم الصورة>

حقل Link Library Member يشبه عمود المفتاح الخارجي في أطر العمل الأخرى. سيسمح لك بربط القيمة بسجل في نوع مستند آخر. في هذه الحالة، يرتبط بسجل من نوع المستند Library Member.

حقل Full Name هو حقل للقراءة فقط سيتم جلب قيمته تلقائيًا من حقل full_name في السجل المرتبط Library Member.

الآن، انتقل إلى قائمة Library Membership وأنشئ مستندًا جديدًا. ستلاحظ أن حقل Library Member هو قائمة منسدلة تعرض السجلات الحالية كخيارات. اختر عضو مكتبة وسيتم جلب الاسم الكامل تلقائيًا. رائع، أليس كذلك؟

أنواع المستندات المرتبطة

أنواع المستندات المرتبطة هي أنواع مستندات يتم ربطها في أنواع مستندات أخرى كحقول Link. جميع أنواع المستندات قابلة للربط. يمكننا تصنيف أنواع المستندات على نطاق واسع إلى ماستر (Master) و معاملات (Transactional) بناءً على نوع البيانات التي تخزنها. Article، Library Member هي أمثلة على بيانات ماستر لأنها تمثل كيانًا (ماديًا أو افتراضيًا). Library Membership هو مثال على نوع مستند يخزن بيانات معاملات.

أنواع المستندات القابلة للإرسال

عند تمكين Is Submittable في نوع مستند، يصبح نوع مستند قابل للإرسال. يمكن أن يكون لنوع المستند القابل للإرسال 3 حالات: مسودة (Draft)، مرسل (Submitted) و ملغي (Cancelled). يمكن تغيير المستند في حالة مسودة مثل أي مستند، ولكن بمجرد أن يكون في حالة مرسل، لا يمكن تغيير قيمة أي حقل في المستند. يمكن إلغاء المستند المرسل، مما يجعل المستند غير صالح. إذا لاحظت، تمت إضافة حقل إضافي في نوع مستند Library Membership خاصتنا يسمى Amended From. يُستخدم هذا الحقل لتتبع التعديلات في المستندات. بمجرد إلغاء مستند، لا يمكن إلا تعديله (Amended)، مما يعني أنه يمكن تكراره وسيتم ربط المستند الملغي بالمستند المعدل الجديد عبر حقل Amended From.

التحقق من وحدة التحكم للعضوية

الآن، دعنا نكتب كودًا للتأكد من أنه كلما تم إنشاء Library Membership، لا توجد عضوية نشطة للعضو.

library_membership.py

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

class LibraryMembership(Document):
    # التحقق قبل إرسال هذا المستند
    def before_submit(self):
        exists = frappe.db.exists(
            "Library Membership",
            {
                "library_member": self.library_member,
                "docstatus": DocStatus.submitted(),
                # تحقق مما إذا كان تاريخ انتهاء العضوية لاحقًا لتاريخ بدء هذه العضوية
                "to_date": (">", self.from_date),
            },
        )
        if exists:
            frappe.throw("There is an active membership for this member")

كتبنا منطقنا في طريقة before_submit التي ستعمل قبل إرسال المستند. استخدمنا طريقة frappe.db.exists للتحقق مما إذا كان سجل Library Membership موجودًا مع عوامل التصفية المقدمة. إذا كان موجودًا، استخدمنا frappe.throw لإيقاف تنفيذ البرنامج برسالة ستظهر لإعلام المستخدم بالسبب.

الآن، حاول إنشاء Library Membership بفترة متداخلة ويجب أن ترى خطأ عند إرسال المستند.

<اسم الصورة>

معاملة المكتبة

لنقم بإنشاء نوع مستند لتسجيل إصدار أو إعادة مقالة بواسطة عضو مكتبة لديه عضوية نشطة.

سيكون هذا النوع المستند يسمى Library Transaction وسيكون له الحقول التالية:

  • Article - رابط إلى Article
  • Library Member - رابط إلى Library Member
  • Type - تحديد مع خيارين: Issue و Return
  • Date - تاريخ المعاملة

سيكون هذا النوع المستند أيضًا قابل للإرسال (Submittable).

<اسم الصورة>

التحقق من صحة المعاملة

عند إصدار مقالة، يجب التحقق مما إذا كان عضو المكتبة لديه عضوية نشطة. يجب علينا أيضًا التحقق مما إذا كانت المقالة متاحة للإصدار. دعنا نكتب الكود لعمليات التحقق هذه.

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()
            # تعيين حالة المقالة لتكون "Issued"
            article = frappe.get_doc("Article", self.article)
            article.status = "Issued"
            article.save()

        elif self.type == "Return":
            self.validate_return()
            # تعيين حالة المقالة لتكون "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)
        # لا يمكن إصدار المقالة إذا كانت مُصدرة بالفعل
        if article.status == "Issued":
            frappe.throw("Article is already issued by another member")

    def validate_return(self):
        article = frappe.get_doc("Article", self.article)
        # لا يمكن إعادة المقالة إذا لم يتم إصدارها أولاً
        if article.status == "Available":
            frappe.throw("Article cannot be returned without being issued first")

    def validate_membership(self):
        # تحقق مما إذا كانت هناك عضوية صالحة موجودة لعضو المكتبة هذا
        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")

هناك الكثير من التعليمات البرمجية هنا ولكن يجب أن تكون ذاتية الشرح. توجد تعليقات برمجية مضمنة لمزيد من الشرح.

إعدادات المكتبة

لنقم بإنشاء آخر نوع مستند لتطبيقنا: Library Settings. سيكون له الحقول التالية:

  • Loan Period - سيحدد فترة الاستعارة بعدد الأيام
  • Maximum Number of Issued Articles - يقيد الحد الأقصى لعدد المقالات التي يمكن إصدارها بواسطة عضو واحد

نظرًا لأننا لا نحتاج إلى وجود سجلات متعددة لهذه الإعدادات، سنقوم بتمكين Is Single لنوع المستند هذا.

<اسم الصورة>

بعد إنشاء نوع المستند، انقر على Go to Library Settings، للانتقال إلى النموذج وتعيين قيم Loan Period و Maximum Number of Issued Articles.

أنواع المستندات المفردة

عند تمكين Is Single في نوع مستند، يصبح نوع مستند مفرد (Single DocType). نوع المستند المفرد يشبه السجلات الفردية (singleton) في أطر العمل الأخرى. لا ينشئ جدول قاعدة بيانات جديد. بدلاً من ذلك، يتم تخزين جميع القيم المفردة في جدول واحد يسمى tabSingles. يُستخدم عادةً لتخزين الإعدادات العامة (global settings).

التحقق من صحة إعدادات المكتبة

دعنا نجري التغيير في Library Membership بحيث يتم حساب To Date تلقائيًا بناءً على Loan Period و From Date.

library_membership.py

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

import frappe

class LibraryMembership(Document):
    # التحقق قبل إرسال هذا المستند
    def before_submit(self):
        exists = frappe.db.exists(
            "Library Membership",
            {
                "library_member": self.library_member,
                "docstatus": DocStatus.submitted(),
                # تحقق مما إذا كان تاريخ انتهاء العضوية لاحقًا لتاريخ بدء هذه العضوية
                "to_date": (">", self.from_date),
            },
        )
        if exists:
            frappe.throw("There is an active membership for this member")

        # الحصول على فترة الاستعارة وحساب to_date عن طريق إضافة loan_period إلى 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 Settings.

الآن، دعنا نجري التغيير في Library Transaction بحيث عند إصدار Article، يتحقق مما إذا تم الوصول إلى الحد الأقصى.

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()  # <-- تمت إضافة التحقق الجديد
            # تعيين حالة المقالة لتكون "Issued"
            article = frappe.get_doc("Article", self.article)
            article.status = "Issued"
            article.save()

        elif self.type == "Return":
            self.validate_return()
            # تعيين حالة المقالة لتكون "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)
        # لا يمكن إصدار المقالة إذا كانت مُصدرة بالفعل
        if article.status == "Issued":
            frappe.throw("Article is already issued by another member")

    def validate_return(self):
        article = frappe.get_doc("Article", self.article)
        # لا يمكن إعادة المقالة إذا لم يتم إصدارها أولاً
        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):
        # تحقق مما إذا كانت هناك عضوية صالحة موجودة لعضو المكتبة هذا
        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 لحساب عدد المعاملات التي أجراها العضو.

وبهذا، قمنا بتغطية أساسيات إنشاء نوع المستند وأنواع نوع المستند. كما كتبنا منطق الأعمال لأنواع المستندات المختلفة.

أحسنت عملًا في الوصول إلى هذا الحد. دعنا نكمل.

Discard
Save

On this page

Review Changes ← Back to Content
Message Status Space Raised By Last update on