提交时允许

提交后允许编辑

提交后允许编辑 是一个 DocField 属性,它允许字段在文档提交后仍可编辑。已提交的文档通常会被锁定以保护审计跟踪。仅当字段可以安全更改,且不会改变已提交文档的含义、价值、状态或分类账影响时,才应使用此选项。

何时使用“提交后允许编辑”

为那些在提交后记录附加信息,但不影响验证、总计、库存、会计、工作流决策或外部承诺的字段启用此属性。

  • 提交后收到的参考编号
  • 备注、说明或内部评论
  • 跟踪详情或发货信息
  • 仅用于打印的标签或操作元数据

何时不应使用

不要为影响业务逻辑或已提交文档的财务、库存或合规含义的字段启用“提交后允许编辑”。如果更改某个字段应改变交易,则应取消并修改文档,而不是直接编辑。

  • 金额、数量、费率、税费、折扣或总计
  • 往来单位、公司、过账日期、仓库、项目、科目或货币字段
  • 用于工作流、集成、报告或验证的状态字段
  • 任何用于创建分类账分录或库存分类账分录的字段

使用“自定义表单”启用“提交后允许编辑”

  1. 转到 自定义表单
  2. 选择需要在提交后保持可编辑状态的 DocType。

表单构建器将打开并显示所选 DocType 的字段。在此示例中,选择了 待办事项 DocType。

  1. 在字段表中,选择提交后应保持可编辑状态的字段。
  2. 在字段属性中,启用 提交后允许编辑
  3. 点击 更新 以保存自定义设置。

使用属性搜索可以快速找到 提交后允许编辑,尤其是在字段具有许多属性时。

验证行为

  1. 打开您自定义的 DocType 的一个已提交文档。
  2. 确认只有预期的字段可编辑。
  3. 更改字段值并保存文档。
  4. 检查总计、分类账分录、工作流状态以及其他已提交的值是否未发生变化。

在启用“提交后允许编辑”之前,请检查该字段是否在控制器方法、自定义脚本、报告、打印格式、工作流、集成或权限逻辑中使用。如果该字段在任何地方用于计算或决定重要事项,请在提交后保持其锁定状态。

对于自定义应用程序,还应审查服务器端验证。允许在已提交的文档上编辑字段并不会自动使所有相关的业务规则变得安全。请在需要的地方添加验证,以便只允许预期的提交后更新。

故障排除

启用“提交后允许编辑”后,字段仍为只读

重新加载表单,并检查该字段是否也受只读依赖项、角色权限、工作流状态、自定义脚本或服务器端逻辑的控制。

表单上的值已更改,但无法保存

检查该 DocType 的自定义验证和控制器钩子。服务器端验证可能仍会阻止对已提交文档的更新。

该字段应影响交易

不要使用“提交后允许编辑”。请取消并修改已提交的文档,以便更正后的交易具有清晰的审计跟踪。

单据类型布局

文档类型布局

文档类型布局允许您为同一文档类型创建多个表单视图——每个视图可以有不同的字段标签、可见性规则和默认值——而无需修改文档类型本身。布局会根据文档状态自动激活,因此无需手动操作即可显示正确的视图。

常见用例

  • 隐藏高级字段的简化数据录入视图
  • 重新标记字段以提高清晰度的侧重阅读的审阅视图
  • doc.is_return == 1 时自动切换到“退货”布局
  • 由条件或工作区链接驱动的状态或上下文特定布局

创建布局

转到文档类型布局列表,然后点击新建

字段 描述
标题 在布局指示器和面包屑中显示的显示名称(例如 紧凑视图)。
文档类型 此布局适用的文档类型。设置后无法更改。
条件 可选的 JS 表达式。当表达式为真时,此布局会在表单刷新时自动激活。
基于 从同一文档类型的另一个布局继承字段配置。
是子表 将此布局标记为子表布局。选中后,它将出现在其他文档类型布局的子布局选择器中。
默认打印格式 当此布局激活时,预选此打印格式。
默认电子邮件模板 当此布局激活时,预选此电子邮件模板。

选择文档类型后,点击同步字段以拉取文档类型中的所有字段。一个可视化表单构建器将在父布局选项卡中打开,您可以在其中重新排序和配置字段。

覆盖字段属性

在表单构建器中,选择任意字段以覆盖其属性。以下属性可以按布局设置,而不会影响基础文档类型:

属性 效果
label 为此布局重命名字段
hidden 隐藏字段
reqd 将字段设为必填
read_only 将字段设为只读
bold 强调标签
allow_in_quick_entry 在快速录入对话框中包含此字段
in_list_view 在列表视图中将字段显示为列
in_standard_filter 在标准筛选器栏中包含此字段
default 为新文档设置默认值
description 覆盖帮助文本
depends_on 条件可见性表达式
mandatory_depends_on 条件必填表达式
read_only_depends_on 条件只读表达式

布局中未设置的属性将回退到基础文档类型的定义。

自动布局切换

布局选择完全由条件驱动——没有手动切换器。设置一个条件,使布局在表单刷新后表达式评估为真时自动激活:

// Activates for return transactions
doc.is_return == 1

// Activates for high-value orders
doc.grand_total > 100000

// Activates for a specific status
doc.status === "Closed"

当条件匹配时,布局将被应用,并且 URL 会更新为 ?layout=<name></name>。第一个匹配的布局优先;布局按其在列表中出现的顺序进行评估。每当非默认布局激活时,文档标题旁边会出现一个布局指示器徽章。

当布局激活时,面包屑栏会反映出来:

  • 表单视图中,布局标题作为可点击的面包屑显示在文档类型和文档名称之间。点击它会打开按布局条件过滤的列表(从 doc.field OP value 表达式解析)。
  • 在该过滤列表上,布局标题显示为不可点击的面包屑,以便活动上下文保持可见。
  • 当布局激活时,点击文档类型面包屑会清除所有筛选器并移除布局面包屑,返回到未过滤的列表。

子表布局

布局可以为其子表指定不同的布局。在子布局选项卡中,为您要自定义的每个子表添加一行:

字段 描述
表字段名 父文档类型中子表的字段名
子布局 一种文档类型布局,其文档类型与子表的文档类型匹配

当父布局处于活动状态时,每个子表网格会自动使用其指定的子布局进行渲染——应用与父布局相同的字段级覆盖。

重要提示。 子布局的文档类型必须与子表的文档类型匹配(例如 Sales Invoice Item),而不是父文档类型。请先为子表创建单独的布局,然后在此处引用它。

父布局上可用的相同字段属性——labelhiddenreqdread_onlydepends_on、列顺序等——在子布局激活时同样适用于网格内部。

示例。 一个财务视图父布局将项目表映射到紧凑项目子布局,该子布局隐藏了 descriptionuom 和其他明细列,仅保留 item_codeqtyamount 可见。切换到父布局会自动压缩网格——用户无需额外操作。

布局继承

使用基于来继承同一文档类型的另一个布局的字段配置。子布局以父布局的字段顺序和覆盖为起点,并可以进一步自定义单个字段或添加新字段。

这对于创建一系列相关布局非常有用:先定义一个包含通用字段顺序的基础布局,然后创建继承并调整它的专门布局(例如销售视图财务视图)。

保持字段同步

同步字段按钮会将布局的字段列表与当前文档类型定义进行比较,并报告:

  • 已添加 — 文档类型中存在但布局中缺少的字段(添加到末尾)
  • 已移除 — 布局中存在但文档类型中已不再存在的字段(已删除)

在修改底层文档类型后运行此操作,以保持布局的一致性。

工作区集成

侧边栏工作区中的任何文档类型链接都可以绑定到特定布局。以编辑模式打开工作区,选择一个文档类型链接,然后将文档类型布局字段设置为您想要的布局。点击该链接会打开预先限定为该布局的列表(URL 携带 ?layout=<name></name>),并且从列表创建的新文档会在该布局下打开。

面向开发者:标准布局

通过设置为标准并选择模块,布局可以作为应用的一部分发布。在开发者模式下,保存标准布局会自动将其导出为 JSON 文件到:

{app}/{module}/doctype/{doctype}/doctype_layout/{layout_name}.json

该文件包含在迁移中,并通过 bench migrate 自动部署。标准布局在生产实例(开发者模式之外)上是只读的。

权限类型

注意:此功能自 v16 版本起可用。

自定义权限类型允许你创建超出内置权限(readwritecreatedeletesubmit)之外的权限标志。可用于特定操作的权限,例如“审批记录”、“下载文件”或“模拟用户”。

工作原理

共分为三个步骤:

  1. 开发者创建权限类型:在开发模式下为 DocType 创建权限类型
  2. 开发者添加权限检查:在操作发生的代码中添加权限检查
  3. 系统管理员分配权限给角色:在 Desk 界面中为角色分配权限

权限类型会作为 fixture 保存,并随你的应用一起发布。

示例:在发票上创建“审批”权限

步骤 1:创建权限类型(开发者)

  1. 启用开发者模式并以管理员身份登录
  2. 创建一条新的 权限类型 记录
  3. 权限类型 设置为 approve,将 DocType 设置为 Invoice
  4. 保存

权限类型现已创建,并将随你的应用一起导出。

步骤 2:在代码中添加权限检查(开发者)

在你的发票控制器或处理审批的函数中,检查该权限:

@frappe.whitelist()
def approve_invoice(invoice_name):
    invoice = frappe.get_doc("Invoice", invoice_name)
    if not frappe.has_permission(invoice, "approve"):
        frappe.throw("Not permitted to approve", frappe.PermissionError)
    invoice.approval_status = "Approved"
    invoice.save()

你也可以在不抛出错误的情况下进行检查:

if frappe.has_permission(invoice, "approve"):
    # show approve button on frontend

步骤 3:分配给角色(系统管理员)

应用安装后,系统管理员将权限分配给角色:

  1. 打开 角色权限管理器
  2. 选择发票 DocType
  3. 对于每个角色,勾选或取消勾选“审批”列,操作方式与其他权限相同
  4. 该权限现在将应用于所有拥有该角色的用户

就这样。拥有“审批”权限的用户现在可以执行该操作。没有该权限的用户将看到错误提示。

如何检查权限

在你的 Python 代码中使用 frappe.has_permission()

# Check document-level permission
if frappe.has_permission(invoice_doc, "approve"):
    # User can approve this invoice

# Check DocType-level permission
if frappe.has_permission("Invoice", "approve"):
    # User can approve any invoice

数据脱敏

注意:此功能仅在夜间版(v16)中可用。此功能被视为实验性功能。

在 Frappe 框架中,可以使用权限级别来控制字段的可见性。

然而,在许多业务场景中,显示某些字段但同时隐藏其敏感数据非常重要。例如:

  • 人力资源用户可能需要查看员工详细信息,但不应看到薪资数额。
  • 支持人员可能会看到以掩码格式显示的客户电话号码,如 811XXXXXXX
  • 财务用户可以查看银行账户字段,而无需暴露完整的账号。

这就是数据掩码的作用所在。

使用数据掩码,您可以根据用户的角色和权限配置特定字段以显示掩码或隐藏的值——而无需限制字段的可见性。
它确保敏感信息得到保护,同时保持用户界面的一致性和信息丰富性。

启用数据掩码

可以直接从 DocType 或通过自定义表单启用数据掩码。

启用数据掩码的步骤

  1. 打开要启用掩码的 DocType(在开发者模式下)或自定义表单。
  2. 选择要掩码的字段。
  3. 勾选“掩码”复选框。
  4. 保存并重新加载表单。

启用后,没有该字段“掩码”权限的用户将看到掩码或隐藏的数据,而具有相应权限的用户将看到实际值。

以下是一个快速演示,展示如何启用数据掩码:

支持的字段类型

数据掩码只能应用于以下字段类型:

SelectRead OnlyPhonePercentPasswordLinkIntFloat
Dynamic LinkDurationDatetimeCurrencyDataDate

面向开发者

Frappe 框架中的数据掩码与现有的权限系统无缝协作。
当用户对某个字段没有所需的“掩码”权限时,框架会自动在用户界面和后端响应中将实际值替换为掩码版本。

内部工作原理

  1. 字段配置
    每个字段都可以在其 DocField 中启用 mask 属性。

    {
        "fieldname": "phone_number",
        "fieldtype": "Data",
        "options": "Phone",
        "mask": 1
    }
    
  2. 权限检查
    系统使用以下方法检查当前用户是否具有该字段的 mask 权限:

    meta.has_permlevel_access_to(fieldname=df.fieldname, df=df, permission_type="mask")
    
  3. 响应中的自动掩码
    一旦权限检查失败,框架会在返回字段值之前自动对其进行掩码处理。
    这适用于:

    • 表单视图数据加载
    • 列表视图查询
    • 使用 ORM 或标准数据获取的报表
    • API 响应(例如 /api/resource/.../api/method/...

注意:数据掩码不会自动应用于使用原始 SQL 的自定义 SQL 查询或查询报表。
在这种情况下,开发者需要在返回响应之前,在查询结果中显式应用掩码逻辑。

自定义文档类型

如果您在多个站点(租户)中使用同一个应用程序,每个站点可能希望在 DocType 之上进行特定的自定义。例如,如果您有一个“客户”DocType,每个用户可能希望添加自定义字段、命名或其他针对他们特定的配置。

为了实现站点特定的自定义,Frappe Framework 提供了多种方法:

  1. 自定义字段:用于跟踪站点特定字段的 DocType。
  2. 属性设置器:用于跟踪 DocType 及其子项中被覆盖的特定属性。
  3. 自定义表单:帮助您轻松自定义 DocType 的视图。
  4. 客户端脚本:额外的客户端事件处理程序。
  5. 服务器脚本:额外的服务器端业务逻辑。
  6. 自定义 DocPerm:额外的权限(通过角色权限管理器处理)

自定义表单

自定义表单是一个视图,可帮助您通过单一视图覆盖 DocType 的属性并添加自定义字段。

当您通过自定义表单更改 DocType 的任何属性时,它不会更改底层的 DocType,而是添加新的自定义对象来覆盖这些属性。这是以无缝方式完成的。

在版本 13 中新增

您还可以通过自定义表单添加/编辑链接和操作。这些更改保存在相同的 DocType(DocType LinkDocType Action)中,但会勾选 custom 属性。

这些额外的(自定义)配置会在通过 frappe.get_meta 获取元数据时自动应用。

操作与链接

在版本 12.1 中新增

操作和链接(也称为连接)是为终端用户提供与文档更多交互的两种方式。下图展示了它们是什么:

操作

一个 DocType 可能有一些 DocType Action,这会导致在 DocType 视图上生成一个按钮。支持的操作有:

  1. 服务器操作:这将触发一个已列入白名单的服务器操作。
  2. 路由:这将重定向到给定的路由。

操作配置

在自定义应用中配置操作

要在您自己的应用中调用操作,您需要一个使用 frappe.whitelist 装饰的 Python 函数:

import frappe

@frappe.whitelist()
def execute_function(*args,**kwargs):
    """
 This function will be executed when the Execute Action Button will be clicked
 """
    print('Hello World')
    # The data is transmitted via keyword argument
    print(kwargs)

此代码应放在您应用中的某个位置,通常放在类似 apps/my_app/my_app/api.py 的文件中

然后,配置相应的操作路径:

连接(关联文档)

DocType 视图的一个标准导航辅助工具是仪表板上的 Connections 部分。这有助于查看者一目了然地识别哪些文档类型与此 DocType 相关联,并可以快速创建新的相关文档。

这些链接还支持添加内部链接(指向子表中的 DocType 的链接)。

配置连接

通过脚本

要为您的应用中的文档类型配置连接,请在 <doctype>_dashboard.py</doctype> 中创建一个 get_data() 函数。以下示例来自 ERPNext 中销售发票文档类型的 sales_invoice_dashboard.py

from frappe import _

def get_data():
    return {
        "fieldname": "sales_invoice",
        "non_standard_fieldnames": {
            "Delivery Note": "against_sales_invoice",
            "Journal Entry": "reference_name",
            "Payment Entry": "reference_name",
            "Payment Request": "reference_name",
            "Sales Invoice": "return_against",
            "Auto Repeat": "reference_document",
            "Purchase Invoice": "inter_company_invoice_reference",
        },
        "internal_links": {
            "Sales Order": ["items", "sales_order"],
            "Timesheet": ["timesheets", "time_sheet"],
        },
        "internal_and_external_links": {
            "Delivery Note": ["items", "delivery_note"],
        },
        "transactions": [
            {
                "label": _("Payment"),
                "items": [
                    "Payment Entry",
                    "Payment Request",
                    "Journal Entry",
                    "Invoice Discounting",
                    "Dunning",
                ],
            },
            {"label": _("Reference"), "items": ["Timesheet", "Delivery Note", "Sales Order"]},
            {"label": _("Returns"), "items": ["Sales Invoice"]},
            {"label": _("Subscription"), "items": ["Auto Repeat"]},
            {"label": _("Internal Transfers"), "items": ["Purchase Invoice"]},
        ],
    }
  • transactions 定义所有连接以及它们所属的相应分组。
  • internal_links 定义文档类型具有内部链接的连接。例如,销售发票通过其项目子表与销售订单文档类型具有内部链接。
  • internal_and_external_links 定义文档类型同时具有内部和外部链接的连接。例如,销售发票通过其项目子表与交货单文档类型具有内部链接,并且销售发票也通过其项目子表与交货单文档类型具有外部链接。
  • fieldname 定义在查找文档类型的外部链接时要搜索的默认字段名。
  • non_standard_fieldnames 定义在查找文档类型的外部链接时要搜索的字段名及其相应的文档类型。

这将产生以下连接:

DocType 操作和链接可通过自定义表单进行扩展

虚拟文档类型

虚拟 DocType 是 DocType 的一个功能扩展,允许开发者创建具有自定义数据源和 DocType 控制器的 DocType。其目的是在系统中定义自定义 DocType,而无需在数据库中创建表,同时利用框架提供的前端、资源 API 以及角色和权限功能。

这些虚拟 DocType 在前端的行为与普通 DocType 完全一致,对最终用户来说无法区分,但为开发者提供了对 DocType 数据源的更多控制。借助这一特性,虚拟 DocType 的数据源可以是任何内容:外部 API、辅助数据库、JSON 或 CSV 文件等。这使得开发者能够接入除 MariaDB 和 Postgres 之外的其他数据库后端,让 Frappe 框架变得更加强大!

注意:frappe.db.* 调用仅适用于站点数据库连接。您需要实现相应方法,以直接查询虚拟 DocType 所使用的数据存储。

创建虚拟 DocType

要创建虚拟 DocType,只需在创建 DocType 时勾选“虚拟 DocType”复选框即可:

创建自定义控制器

例如,以下控制器代码使用 JSON 文件作为 DocType 的数据源:

class VirtualDoctype(Document):
    """This is a virtual doctype controller for demo purposes.

 - It uses a single JSON file on disk as "backend".
 - Key is docname and value is the document itself.

 Example:
 {
 "doc1": {"name": "doc1", ...}
 "doc2": {"name": "doc2", ...}
 }
 """

    DATA_FILE = "data_file.json"

 @staticmethod
    def get_current_data() -> dict[str, dict]:
        """Read data from disk"""
        if not os.path.exists(VirtualDoctype.DATA_FILE):
            return {}

        with open(VirtualDoctype.DATA_FILE) as f:
            return json.load(f)

 @staticmethod
    def update_data(data: dict[str, dict]) -> None:
        """Flush updated data to disk"""
        with open(VirtualDoctype.DATA_FILE, "w+") as data_file:
            json.dump(data, data_file)

    def db_insert(self, *args, **kwargs):
        d = self.get_valid_dict(convert_dates_to_str=True)

        data = self.get_current_data()
        data[d.name] = d

        self.update_data(data)

    def load_from_db(self):
        data = self.get_current_data()
        d = data.get(self.name)
        super(Document, self).__init__(d)

    def db_update(self, *args, **kwargs):
        # For this example insert and update are same operation,
        # it might be different for you.
        self.db_insert(*args, **kwargs)

    def delete(self):
        data = self.get_current_data()
        data.pop(self.name, None)
        self.update_data(data)

 @staticmethod
    def get_list(args):
        data = VirtualDoctype.get_current_data()
        return [frappe._dict(doc) for name, doc in data.items()]

 @staticmethod
    def get_count(args):
        data = VirtualDoctype.get_current_data()
        return len(data)

 @staticmethod
    def get_stats(args):
        return {}

您可以在接口文件中了解接口要求及详细说明。要将其他数据源与虚拟 DocType 集成,您需要添加定义数据库访问方式的控制器方法。

结果

虚拟 DocType 的前端保持不变

框架定义的所有 /api/resource 方法均与虚拟 DocType 兼容。

自版本 13 起新增

单一单据类型

单一 DocType 是一种在数据库中仅有一个实例的 DocType。它适用于
持久化存储诸如系统设置这类不需要多条
记录的数据。

>>> settings = frappe.get_doc('System Settings')
>>> settings.notification_frequency
'Daily'

数据结构

单一 DocType 存储在数据库的 tabSingles 表中,每个属性都有自己对应的记录。

列:

  • doctype
  • field
  • value

子表文档类型

到目前为止,我们只见过每个字段只能有一个值的 DocType。
然而,有时可能需要在一个记录下存储多条记录,这也被称为
多对一关系。子 DocType 是一种只能链接到父 DocType 的文档类型。
要创建子 DocType,请在创建文档类型时勾选 是否为子表

要将子 DocType 链接到其父 DocType,请在父 DocType 中添加一行,字段类型
选择 ,选项设置为 子表

子 DocType 记录直接附加到父文档上。

>>> person = frappe.get_doc('Person', '000001')
>>> person.as_dict()
{
 'first_name': 'John',
 'last_name': 'Doe',
 'qualifications': [
 {'title': 'Frontend Architect', 'year': '2017'},
 {'title': 'DevOps Engineer', 'year': '2016'},
 ]
}

子属性

子文档具有一些特殊属性,用于定义它们与父文档的关系:

  • parent:父文档的名称。
  • parenttype:父文档的 DocType。
  • parentfield:父文档中链接该子文档的字段。
  • idx:序号(行)。

表单与视图设置

视图设置

标题字段

DocType 中的一个字段,将作为表单中的标题显示

设置标题字段

在“标题字段”中输入自定义字段的名称

在链接字段中显示标题

您可以启用在链接字段中显示标题,以便在其他 DocType 的链接字段中显示标题,而不是名称

因此,如果在另一个 DocType 中(或通过自定义表单)添加了“链接”类型的自定义字段,则该字段将显示链接文档的标题,而不是名称。