基本指令 - 执行策略

对于基本指令,模块a-detail提供了统一的对外API接口,有如下好处:

  1. 封装数据库事务配置
  2. 封装权限判断
  3. 简化业务Bean组件的开发

基本指令 - 执行链

delete指令为例,指令执行链如下:

1. 模块a-detail:提供API路由

src/module-system/a-detail/backend/src/routes.js

{ method: 'post', path: 'detail/delete', controller: 'detail', middlewares: 'transaction',
  meta: { right: { type: 'detail', action: 4 } },
},

2. 任何前端模块:通过/a/detail/atom/delete调用后端API接口

src/module-system/a-detail/front/src/components/detail/action.js

const flowTaskId = (meta && meta.flowTaskId) || 0;
const key = { detailId: item.detailId, detailItemId: item.detailItemId };
await ctx.$api.post('/a/detail/detail/delete', { flowTaskId, key });

3. 模块a-detail: API路由对应Controller方法,组织好参数调用Service方法

src/module-system/a-detail/backend/src/controller/detail.js

async delete() {
  await this.ctx.service.detail.delete({
    key: this.ctx.request.body.key,
    user: this.ctx.state.user.op,
  });
  this.ctx.success();
}

4. 模块a-detail: Service方法调用ctx.bean.detail方法

async delete({ key, user }) {
  return await this.ctx.bean.detail.delete({ key, user });
}

5. 模块a-detail: ctx.bean.detail调用具体业务的bean方法

src/module-system/a-detail/backend/src/bean/bean.detail.js

async delete({ key, target, user }) {
  const detailClass = await ctx.bean.detailClass.getByDetailId({ detailId: key.detailId });
  if (!detailClass) ctx.throw.module('a-base', 1002);
  if (!key.detailItemId) key.detailItemId = detailClass.detailItemId;
  // delete
  await this._delete2({ detailClass, key, target, user });
}

async _delete2({ detailClass, key, target, user }) {
  // detail bean
  const _moduleInfo = mparse.parseInfo(detailClass.module);
  const _detailClass = ctx.bean.detailClass.detailClass(detailClass);
  const beanFullName = `${_moduleInfo.relativeName}.detail.${_detailClass.bean}`;
  // delete
  await ctx.executeBean({
    beanModule: _moduleInfo.relativeName,
    beanFullName,
    context: { detailClass, target, key, user },
    fn: 'delete',
  });
}

    1. 通过key获取业务数据的detailClass信息
    1. 通过detailClass信息构造beanFullName
    1. 通过beanFullName执行ctx.executeBean,从而调用业务模块提供的业务Bean组件。参见:跨模块调用本地Bean:ctx.executeBean

业务Bean组件

由以上基本指令的执行链可以看到,针对具体的业务开发,我们只需要针对明细类型提供一个业务Bean组件即可。所有通用的核心逻辑都由模块a-detail进行封装,我们只需要在业务Bean组件中提供与业务相关的自定义逻辑即可

定义Bean组件

src/module/test-flow/backend/src/bean/detail.purchaseOrder.js

module.exports = app => {

  class Detail extends app.meta.DetailBase {

    async create({ atomKey, detailClass, item, user }) {
      // super
      const key = await super.create({ atomKey, detailClass, item, user });
      // add purchaseOrder detail
      const res = await this.ctx.model.purchaseOrderDetail.insert({
        atomId: atomKey.atomId,
        detailId: key.detailId,
      });
      // return key
      return { detailId: key.detailId, detailItemId: res.insertId };
    }

    async read({ detailClass, options, key, user }) {
      // super
      const item = await super.read({ detailClass, options, key, user });
      if (!item) return null;
      // meta
      this._getMeta(item);
      // ok
      return item;
    }

    async select({ atomKey, detailClass, options, items, user }) {
      // super
      await super.select({ atomKey, detailClass, options, items, user });
      // meta
      for (const item of items) {
        this._getMeta(item);
      }
    }

    async write({ detailClass, target, key, item, options, user }) {
      // super
      await super.write({ detailClass, target, key, item, options, user });
      // update purchaseOrder detail
      const data = await this.ctx.model.purchaseOrderDetail.prepareData(item);
      data.id = key.detailItemId;
      // update
      await this.ctx.model.purchaseOrderDetail.update(data);
    }

    async delete({ detailClass, target, key, user }) {
      // delete purchaseOrder detail
      await this.ctx.model.purchaseOrderDetail.delete({
        id: key.detailItemId,
      });
      // super
      await super.delete({ detailClass, target, key, user });
    }

    _getMeta(item) {
      // flags
      const flags = [];
      if (item.quantity > 1) {
        flags.push(item.quantity);
      }
      const amount = (item.amount / 100).toFixed(2);
      flags.push(amount);
      // meta
      const meta = {
        summary: item.detailCode,
        flags,
      };
      // ok
      item._meta = meta;
    }

  }

  return Detail;
};

业务Bean组件必须继承基类app.meta.DetailBase

注册Bean组件

src/module/test-flow/backend/src/beans.js

const detailPurchaseOrder = require('./bean/detail.purchaseOrder.js');

module.exports = app => {
  const beans = {
    // detail
    'detail.purchaseOrder': {
      mode: 'app',
      bean: detailPurchaseOrder,
    },
  };
  return beans;
};
注册名称 场景 所属模块 global beanFullName
purchaseOrder detail test-flow false test-flow.detail.purchaseOrder

Bean组件方法

- create

  1. 调用基类方法生成明细Key,得到key.detailId
  2. 新建业务表明细条目
  3. 返回明细Key
名称 说明
atomKey 原子Key
detailClass 明细类型
item 明细条目数据,前端可以通过item传递更多初始化数据
user 当前操作用户

- read

read业务逻辑一般为空,因为这时对象item已经提取了明细的基本表业务表的数据

如果想附加基本表业务表之外的数据,可在这里直接操作对象item

名称 说明
detailClass 明细类型
options 自定义参数
key 明细Key
user 当前操作用户

- select

select业务逻辑一般为空,因为这时对象items已经提取了明细的基本表业务表的数据

如果想附加基本表业务表之外的数据,可在这里直接操作对象items

名称 说明
detailClass 明细类型
options 自定义参数
items 已经检索出来的数据集
user 当前操作用户

- write

  1. 调用基类方法,更新数据表aDetail的基本信息
  2. 更新业务表明细条目
名称 说明
detailClass 明细类型
target 针对什么目标进行write操作
key 明细Key
item 条目数据
options 自定义参数
user 当前操作用户
  • target

一般而言,target参数来自原子write方法。比如,将原子数据从草稿提交至正式副本时,也会同时把该原子关联的明细数据提交至正式副本,此时target值就是formal

名称 说明
(empty) 保存草稿
draft 从历史副本恢复旧版本,或者从正式副本再次编辑
formal 草稿提交至正式副本
history 正式副本推入历史副本
clone 克隆一个新副本,直接进入草稿编辑状态

- delete

  1. 删除业务表明细条目
  2. 调用基类方法,删除数据表aDetail条目
名称 说明
detailClass 明细类型
key 明细Key
user 当前操作用户