自定义表单
在PSI开源进销存中,自定义表单是指具有如下特征的业务单据:
有单号,且单号全局唯一
单据是主从结构,即一个主表+多个子表,其中最常见的是一个主表+一个子表的结构
重要提醒
自定义表单的应用的第一步实际上是数据库建模。自定义表单能快速创建业务单据,但是具体到每张业务单据,其后台的数据库表结构是需要根据业务需求做仔细的数据库建模的。
主表字段引用码表数据
业务单据中引用码表数据,这是一个显而易见的的常用功能。正是因为这个功能,系统数据才能共享和流转。
业务单据中保存码表数据的
id,由此实现引用的基本功能。这类似数据库主外键关系。业务单据中除了保存码表数据的
id之外,还会保存码表的编码(code)和名称(name)。这是冗余数据,但是简化了系统设计。存在码表数据修改后的不一致问题,但是可以通过id来后台维护统一的途径。
业务单据中保存码表id、code和name的惯例
保存
id的字段名称,可以自由命名,但是习惯上以id结尾,例如:supplier_id保存
code和name的字段名称的命名规律是:id字段名_code和id字段名_name。例如:当保存id的字段名为supplier_id的时候,code和name对应的名称分别为supplier_id_code和supplier_id_name。本约定由程序自动生成,不可调整。一个字段当其
值来源被设为引用码表的时候,该字段就被用来保存id,并自动生成code和name字段。注意:code和name字段在元数据中不可见,只存在于数据库表结构中。同时,该字段的编辑器需要指定为psi_codetable_formfield,以便正确地创建UI。
每个业务单据都自带的字段:业务员(biz_user_id),就是典型的业务单据中保存码表id、code和name,参考该字段,可以帮助理解。
更复杂的引用码表数据场景
上述是引用了码表的id、code和name字段。更复杂的场景需要引用码表更多的数据,例如:在采购订单中,用户录入完供应商字段,希望把供应商的联系人、收货地址和电话一并引用过来,并保存在采购订单中。
这是就需要设置码表引用,并建立码表字段和表单字段的对应关系。
明细表字段引用码表数据
明细表字段引用码表数据和主表字段略有不同。差异的原因是:当明细字段引用码表的时候,后台不自动创建code和name字段,所有的字段需要手工创建。
下面以业务单据引用物料这个码表为例,做一个详细说明。
物料这个码表,在给业务单据引用的时候,并不是直接使用t_goods这个表,而是数据库视图v_goods。用数据库视图的原因是:物料计量单位并不在t_goods表中,而业务单据中引用物料通常需要一并引用计量单位。这种使用数据库视图来形成一个码表的做法,是一个基础的应用技巧。
我们假设业务单据需要引用物料的id、编码(code)、名称(name)、规格型号(spec)和计量单位(unit_name)这五个属性的值。
在业务单据的明细表中创建五个字段:goods_id、goods_code、goods_name、goods_spec、 unit_name
goods_id的属性
对用户可见设置为:对用户不可见。 这样在UI上用户就不能看见了。goods_code是核心字段,其
值来源设置为:引用码表。goods_name、goods_spec和unit_name这三个字段是只读性质的,把其
编辑器类型设置为:displayfield设置码表引用中的字段对应关系,如下图所示

子表索引1表示是第一个明细表,表单字段名goods_code上面我们创建的表单字段。
完成上述设置后,表单五个字段:goods_id、 goods_code、 goods_name、 goods_spec、unit_name中就只有goods_code这个字段是用户通过UI录入的,其他字段由系统根据码表引用中的字段对应关系自动赋值。
关于数据冗余字段
引用码表数据的时候,在业务单据本身上创建了很多冗余字段,这样做有如下的一些设计考量:
简化自定义表单展示数据的逻辑,同时提高运行速度。这是典型的空间换时间的做法。
既然是冗余字段,就存在码表数据变更后,数据不一致的问题。可以通过保存的id来同步数据,这需要额外的二次开发。
举一个这种冗余设计的应用场景,某个长期供应商,在合作两年后发生了公司名称变更。前两年的历史单据中的供应商名称通常是不应该改为新的名称的。所以,是否同步历史数据是需要根据业务仔细分析的。
编程扩展自定义表单
用户除了使用配置元数据的方式来扩展自定义表单之外,还可以通过编程来进一步扩展自定义表单。
编程扩展自定义表单不是万能方法,遇到自定义表单不能实现的需求,最终采用手工编码是不可避免的。
前端编程扩展
前端主要是扩展按钮。使用自定义表单中的业务按钮和Mixin(业务按钮扩展库)就能自由新增按钮。
事实上,自定义表单现有的新建、编辑等按钮操作的实现方式就是上述方式。可以通过阅读现有的代码来理解如果扩展按钮。
从技术原理来说,Mixin可以扩展自定义表单前端代码的所有内部方法,但是这目前还处于技术预研阶段,尚不推荐使用。
后端编程扩展
指定自定义表单的业务逻辑类名,就能扩展后台PHP代码。这个设计思路和码表的自定义业务逻辑的方法是一致的。
| 业务逻辑切入点 | 处理函数 | 说明 |
|---|---|---|
| 新建单据 | afterAdd | 函数返回null表示可以继续执行新建操作; 返回字符串则会作为提示信息返回给用户,同时不执行新建操作(数据库事务会回滚) |
| 编辑单据 | afterUpdate | 函数返回null表示可以继续执行编辑操作; 返回字符串则会作为提示信息返回给用户,同时不执行编辑操作(数据库事务会回滚) |
| 删除单据 | beforeDelete | 函数返回null表示可以执行删除操作; 返回字符串则会作为提示信息返回给用户,同时不执行删除操作(数据库事务会回滚) |
| 获取默认值 | getDefaultValues | 函数返回null表示无默认值; 返回array,array中的键是字段名称,对应的就是该字段的默认值 尚未实现 |
| 审核单据 | afterCommit | 函数返回null表示可以执行审核操作; 返回字符串则会作为提示信息返回给用户,同时不执行审核操作(数据库事务会回滚) |
| 取消审核单据 | beforeCancelCommit | 函数返回null表示可以执行取消审核操作; 返回字符串则会作为提示信息返回给用户,同时不执行取消审核操作(数据库事务会回滚) |
范例代码见: Demo
业务扩展类需要使用#[FormPlugin]这个Attribute来标注,避免诸如把码表的扩展类被误设置为表单的扩展类。
表单状态
表单状态是业务单据中重要的后台字段,用于控制工作流。
表单状态(数据库字段名:bill_status),其值取自系统数据字典t_sysdict_sln0000_form_status,这是系统的默认实现,可以根据具体的业务需求调整为自行设计的其他系统数据字典,但是要遵循如下规则:
| 状态值 | 说明 |
|---|---|
| -1 | -1表示全部单据,用于主界面查询条件中使用,实际单据中不存储该值 |
| 0 | 0表示单据处于待审核状态,该状态下,单据实质上草稿状态,可以被编辑和删除 |
| 1000 | 1000表示单据已经被审核,该状态下,单据不能再被编辑和删除,是正式生效的业务单据 |
| 大于1000 | 留给用户扩展,建议从2000开始使用 |
-1、0和1000这三个值在数据字典中必须存在,且不能改变其上述含义
自定义表单中使用
审核来表示单据正式生效,可以参与到业务工作流中了(例如:审核的采购订单才可以生成采购入库单)。
如果要做类似OA系统中的工作流的流转,建议使用审批来表达。
