SuiteCRM 二次开发:基于 logic hook 实现合同回款率的计算

文章目录

    SuiteCRM 的这套 hook 机制还是挺灵活的。就是文档有的少,大部分靠摸索。

    部署时,只能在工作日晚上,或者周末没人使用时才能部署。异常痛苦。

    关联记录存储时相关的 hook

    这个属于 Module Hooks,即模块级的 Hook。

    • after_relationship_add
    • after_relationship_delete

    除了关系的添加和删除,实际上应该还有收款记录的值修改需要监听。

    • after_save

    看起来,在合同模块添加 Hook,不如在收款记录模块添加 Hook 更方便管理。因为统计函数能够复用。

    后续补充:

    这里还是想简单了。关系的添加和收款金额变更这两种情况可以加到收款记录模块的 logic hook 中。

    但是,关系的删除,只能加到合同模块的 logic hook 中,因为关系删除的同时,通常也把收款记录删除了。所以不能放到收款记录模块中。

    记录展示时相关的 hook

    上面是通过更新增加的自定义统计字段实现的回款率计算,还有一种方式是在展示合同时,才查询的做法:

    • process_record:Fired when a record is processed ready to be displayed in list views or dashlets. 及在列表展示时触发
    • after_retrieve:Fired after a record is retrieved from the DB. 需要注意,这个可能会触发多次。相当于每次从数据库去读取时,都会触发。实际测试,在 Account 详情页,刷新一次,被触发三次这个钩子。

    这种做法,是平时自己写程序时的标准做法。

    但是,SuiteCRM 由于钩子机制不可控。

    我觉得用来做简单的数据格式化,没问题。要是用来触发关联记录查询,就得防止造成数据库的查询压力。

    如何确认关联关系的名称

    即 relationship name.

    比如,合同模块关联了收款模块,一条合同记录会关联多条收款记录。

    那么就需要在 logic hook 中知道这个关系的名称是什么,否则无法获取相关的数据。

    查看方法一:

    使用管理员账号,在系统管理中,工作室 - 相关模块 - 关联关系。

    这里就能看到对应的关联名称,即对应模块。

    例如,合同与收款记录的关联关系名为:

    aos_contracts_skgl_shoukuanguanli_1
    

    查看方法二:

    进入 SuiteCRM 项目代码根目录,查看代码文件:

    cache/modules/skgl_shoukuanguanli/skgl_shoukuanguanlivardefs.php

    这个非内置模块,估计是基于中文名字的拼音自动生成的。

    在代码中搜索 relationship 关键词,就能找到跟合同 Contract 的相关信息。

    'aos_contracts_skgl_shoukuanguanli_1' =>
        array (
          'name' => 'aos_contracts_skgl_shoukuanguanli_1',
          'type' => 'link',
          'relationship' => 'aos_contracts_skgl_shoukuanguanli_1',
          'source' => 'non-db',
          'module' => 'AOS_Contracts',
          'bean_name' => 'AOS_Contracts',
          'vname' => 'LBL_AOS_CONTRACTS_SKGL_SHOUKUANGUANLI_1_FROM_AOS_CONTRACTS_TITLE',
          'id_name' => 'aos_contracts_skgl_shoukuanguanli_1aos_contracts_ida',
        ),
    

    对应的,在 cache/modules/AOS_Contracts/AOS_Contractsvardefs.php 中看到的关系信息是:

    'aos_contracts_skgl_shoukuanguanli_1' =>
        array (
          'name' => 'aos_contracts_skgl_shoukuanguanli_1',
          'type' => 'link',
          'relationship' => 'aos_contracts_skgl_shoukuanguanli_1',
          'source' => 'non-db',
          'module' => 'skgl_shoukuanguanli',
          'bean_name' => 'skgl_shoukuanguanli',
          'side' => 'right',
          'vname' => 'LBL_AOS_CONTRACTS_SKGL_SHOUKUANGUANLI_1_FROM_SKGL_SHOUKUANGUANLI_TITLE',
        ),
    

    小知识: AOS 前缀

    从上面的关系信息中可以看到,合同模块的名字为 AOS_Contracts。而合同和联系人分别是 Account 和 Contact。为何有的带 AOS 前缀,有的不带呢?suitecrm AOS prefix 代表什么?

    在 SuiteCRM 中,AOS(Advanced OpenSales)是一个模块,用于管理销售、报价和发票等相关业务。AOS prefix 是指 AOS 模块中用于标识相关数据库表的前缀。在 SuiteCRM 中,数据库表名通常使用特定的前缀来标识所属的模块。AOS prefix 是 AOS 模块相关表的前缀,它通常为 “aos_“。例如,AOS 模块中的报价表的完整数据库表名可能是 “aosquotes”,其中 “aos” 就是 AOS prefix。

    通过使用前缀,SuiteCRM 可以确保不同模块的数据库表之间不会发生冲突,并提供更好的模块化和数据结构管理。

    如何判断具体是哪个 relationship 发生了变化

    以 after_relationship_add 触发为例,单单判断了事件名是否是 after_relationship_add 是不够的。还需要判断具体是新增了哪种关联关系。

    例如,合同模块就好多个关联关系,不能因为一个不相关的关系建立,就每次都把收款记录都重新统计一遍。

    参考 ChatGPT 给出的建议:

    在 after_relationship_add 触发器中,$arguments 参数包含以下键值对:

    • module: 触发关联操作的模块名。
    • related_module: 被关联的模块名。
    • related_id: 被关联记录的 ID。
    • relationship: 关联关系的名称。
    • related_bean: 被关联记录的 Bean 对象。

    而通过 github 上的参考代码,结合实际,发现用 related_module 来判断非常合适。以联系人关联的客户为例:

    public function addPayment($bean, $event, $arguments)
    {
        $GLOBALS['log']->fatal('event: ' . $event);
        $GLOBALS['log']->fatal('related module: ' . $arguments['related_module']);
    
        if ($arguments['related_module'] == 'Accounts') {
            // do something 
        }
    }
    

    自定义字段

    使用自定义字段前,必须显示拉取一次么?

    $project->custom_fields->retrieve();
    

    调试

    网上查到的代码真是千差万别,只能本地搭建一套开发环境,一点点打印日志调试了。
    我觉得直接在服务器上重新部署一个测试版本的更简单方便。

    调试前的备份

    • 备份数据库
    • 备份项目目录

    after_save 失败

    应该是 $argument 参数,不包含 related_module 这个 key。

    果然是这个问题,将这行代码删除即可。

    after_relationship_delete 失败

    现象是,读取不到相关联的合同。

    这里不单是关联关系删除了,同时回款记录也删除了。

    所以读取不到关联关系也很合理。

    正确的做法应该是,去合同模块那里去实现一个 after_relationship_delete 的钩子。

    参考

    关于作者 🌱

    我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊,或者关注我的个人公众号“大象工具”, 查看更多联系方式