Source: model/transaction-instance.js

'use strict';

const _ = require('lodash');
const debug = require('debug')('app:model:transaction-instance');

const ActionProgress = require('../help/ActionProgress');

const STATUS_INIT = 'init';
const STATUS_RUNNING = 'running';
const STATUS_COMPLETED = 'completed';
const STATUS_ABNORMAL = 'abnormal';

const STEP_TRYING = 'trying';
const STEP_CONFIRMING = 'confirming';
const STEP_CANCELLING = 'cancelling';

module.exports = (sequelize, DataTypes, BaseModel) => {
  /**
   * @class model TransactionInstance
   * @extends BaseModel
   */
  class TransactionInstance extends BaseModel {
    /**
     * 实例当前步骤和相关信息的集合
     * @typedef  {Object}  getCurrentStepInfoReturn
     * @property {String}         currentStep                 - 当前实例的步骤
     * @property {Array.<number>} actionIds                   - [当前action的id]
     * @property {Array.<ActionProgress>}  currentActionsInfo - [action进度总结]
     */

    /**
     * 获取实例的当前步骤 和 相关信息集合
     * @return {getCurrentStepInfoReturn}
     */
    getCurrentStepInfo() {
      const instance = this;

      const {
        step,
        tryIds,
        confirmIds,
        cancelIds,
        tryActionsInfo,
        confirmActionsInfo,
        cancelActionsInfo,
      } = instance;

      let actionIds = [];
      let actionsInfo = [];

      switch (step) {
      case STEP_TRYING:
        actionIds = tryIds;
        actionsInfo = tryActionsInfo;
        break;
      case STEP_CONFIRMING:
        actionIds = confirmIds;
        actionsInfo = confirmActionsInfo;
        break;
      case STEP_CANCELLING:
        actionIds = cancelIds;
        actionsInfo = cancelActionsInfo;
        break;
      default:
        break;
      }
      const exportInfo = {
        currentStep: step,
        currentActionIds: actionIds,
        currentActionsInfo: actionsInfo,
      };

      debug('exportInfo :%o', exportInfo);

      return exportInfo;
    }

    /**
     * 实例当前步骤和相关信息的集合
     * @typedef  {Object}  getStepNeedExecReturn
     * @property {String}         step                         - 当前实例的步骤
     * @property {Array.<number>} currentActionIds             - [实例当前待执行的Action]
     * @property {Array.<ActionProgress>}  needExecActionsInfo - [实例当前待执行的Action简介集合]
     */

    /**
     * 获取实例的当前步骤 需要执行的Action 和相关的集合信息
     * @return {getStepNeedExecReturn}
     */
    getStepNeedExec() {
      const instance = this;

      debug('instance :%o', instance);

      const {
        currentStep: step,
        currentActionIds: actionIds,
        currentActionsInfo: actionsInfo,
      } = instance.getCurrentStepInfo();

      const needExecActionsInfo = actionIds.map((actionItem) => {
        const matchAction = _.find(actionsInfo, {
          id: actionItem,
        });

        const padInitAction = {
          id: actionItem,
          success: false,
        };

        return (matchAction && matchAction.success) ? null : (
          new ActionProgress(matchAction || padInitAction));
      }).filter(item => item);

      const needExecActionIds = needExecActionsInfo
        .map(item => !item.success && item.id)
        .filter(item => item);

      const exportInfo = {
        step,
        needExecActionsInfo,
        actionIds: needExecActionIds,
      };

      debug('needExecActionsInfo :%o', needExecActionsInfo);

      return exportInfo;
    }

    /**
     * 将结果信息集合合并生成相应的 `ActionInfo` 字段
     *
     * @param {Object[]} resultInfo                    - 结果信息集合
     * @param {number} resultInfo[].id                 - Action的id
     * @param {boolean} resultInfo[].success           - Action的结果是否成功
     * @param {number} resultInfo[].currentAttemptTime - Action的当前尝试次数
     * @param {number} resultInfo[].maxAttemptTime     - Action的最大允许尝试次数
     * @return {Array.<ActionProgress>} action的进度集合
     */
    mergeCurrentStepUpdateActionInfo(resultInfo) {
      const instance = this;
      const {
        currentActionIds: actionIds,
        currentActionsInfo: actionsInfo,
      } = instance.getCurrentStepInfo();

      const originActionsInfo = _.cloneDeep(actionsInfo);

      /**
       * 已最近更新结果为有限选择
       */
      const nextActionsInfo = actionIds.map((actionId) => {
        const originActionsInfoMatchItem = _.find(originActionsInfo, { id: actionId });
        const resultActionsInfoMatchItem = _.find(resultInfo, { id: actionId });

        let targetItem = originActionsInfoMatchItem || resultActionsInfoMatchItem;

        if (originActionsInfoMatchItem && resultActionsInfoMatchItem) {
          targetItem = originActionsInfoMatchItem.success ? (
            originActionsInfoMatchItem) : {
            ...originActionsInfoMatchItem,
            ...resultActionsInfoMatchItem,
          };
        }
        return targetItem;
      }).filter(item => item).map(item => new ActionProgress(item));

      return nextActionsInfo;
    }


    /**
     * 生成下一步的数据
     * createGoNextData
     * @param {Array.<object>} resultInfo              - 结果信息集合
     * @param {number} resultInfo[].id                 - Action的id
     * @param {boolean} resultInfo[].success           - Action的结果是否成功
     * @param {number} resultInfo[].currentAttemptTime - Action的当前尝试次数
     * @param {number} resultInfo[].maxAttemptTime     - Action的最大允许尝试次数
     */
    createGoNextData(resultInfo) {
      const instance = this;
      const {
        currentStep: originStep,
        currentActionIds,
      } = instance.getCurrentStepInfo();

      const nextUpdateActionInfo = instance.mergeCurrentStepUpdateActionInfo(resultInfo);

      let allActionIsAllCompleted = false; // 所有action 全部成功
      let someActionIsGtattemptLimit = false; // 是否有行为超出最大重试次数


      // all Action is Completed
      allActionIsAllCompleted = currentActionIds.every((itemId) => {
        const matchActionInfo = _.find(nextUpdateActionInfo, { id: itemId });

        return matchActionInfo && matchActionInfo.success;
      });


      // 没有全部通过时, 尝试调用是否有行为超过最大调用次数
      if (!allActionIsAllCompleted) {
        someActionIsGtattemptLimit = currentActionIds.some((itemId) => {
          const matchActionInfo = _.find(nextUpdateActionInfo, { id: itemId, success: false });
          return matchActionInfo && (
            matchActionInfo.currentAttemptTime >= matchActionInfo.maxAttemptTime);
        });
      }

      const updateData = {};

      // 更新步骤信息
      switch (originStep) {
      case STEP_TRYING:
        updateData.tryActionsInfo = nextUpdateActionInfo;
        break;
      case STEP_CONFIRMING:
        updateData.confirmActionsInfo = nextUpdateActionInfo;
        break;
      case STEP_CANCELLING:
        updateData.cancelActionsInfo = nextUpdateActionInfo;
        break;
      default:
        throw new Error(`${instance.id} step error`);
      }

      // 更新 step , status
      if (allActionIsAllCompleted) {
        switch (originStep) {
        case STEP_TRYING:
          updateData.step = STEP_CONFIRMING;
          updateData.tryAllCompleted = true;
          break;
        case STEP_CONFIRMING:
          updateData.status = STATUS_COMPLETED;
          updateData.confirmAllCompleted = true;
          break;
        case STEP_CANCELLING:
          updateData.status = STATUS_COMPLETED;
          updateData.cancelAllCompleted = true;
          break;
        default:
          break;
        }
      }

      // 超出限制,执行取消
      // 如果是 CANCELLING 步骤,则执行异常状态修改
      if (someActionIsGtattemptLimit) {
        switch (originStep) {
        case STEP_TRYING:
          updateData.step = STEP_CANCELLING;
          break;
        case STEP_CONFIRMING:
          updateData.step = STEP_CANCELLING;
          break;
        case STEP_CANCELLING:
          updateData.status = STATUS_ABNORMAL;
          break;
        default:
          break;
        }
      }

      return updateData;
    }
  }

  TransactionInstance.STATUS_INIT = STATUS_INIT;
  TransactionInstance.STATUS_RUNNING = STATUS_RUNNING;
  TransactionInstance.STATUS_COMPLETED = STATUS_COMPLETED;
  TransactionInstance.STATUS_ABNORMAL = STATUS_ABNORMAL;

  TransactionInstance.STEP_TRYING = STEP_TRYING;
  TransactionInstance.STEP_CONFIRMING = STEP_CONFIRMING;
  TransactionInstance.STEP_CANCELLING = STEP_CANCELLING;

  /**
   * setter *ActionsInfo
   * @param {string} fieldName
   */
  function getterActionsInfo(fieldName, idsFieldName) {
    function getterActionsInfoCreateActionProgress() {
      const idsArray = this.getDataValue(idsFieldName);
      const infoArray = this.getDataValue(fieldName);

      return idsArray.map((idItem) => {
        const originActionsInfoMatchItem = _.find(infoArray, { id: idItem });
        if (originActionsInfoMatchItem) {
          return new ActionProgress(originActionsInfoMatchItem);
        }
        return new ActionProgress({ id: idItem });
      });
    }
    return getterActionsInfoCreateActionProgress;
  }

  function setterActionsInfo(fieldName) {
    function setterActionsInfoForamt(array) {
      const instance = this;
      const data = [];

      const len = array.length;
      for (let index = 0; index < len; index += 1) {
        const ele = array[index];
        if (ele && ele.id) {
          const item = {
            id: ele.id,
            success: ele.success || false,
            maxAttemptTime: ele.maxAttemptTime || ActionProgress.DEFAULT_MAX_ATTEMPT_TIME,
            currentAttemptTime: ele.currentAttemptTime || 0,
          };
          data.push(item);
        }
      }

      instance.setDataValue(fieldName, data);
      return data;
    }
    return setterActionsInfoForamt;
  }

  /**
   * TransactionInstance 事务实例
   * 根据请求参数和 `项目(project)`, `进程(process)` 生成的实例
   * 标记每条事务的运行状态和结果
   * @memberof model
   * @namespace TransactionInstance
   * @property {string}           title                               - 项目名称
   * @property {string}           desc                                - 描述
   * @property {string}           messageId                           - 消息id/订单id, 用于标识事务, 每个项目 唯一不重复
   * @property {number}           projectId                           - 对应的项目id
   * @property {number}           proccessId                          - 对应的进程
   * @property {number}           actionId                            - 行为id
   * @property {string}           status                              - 状态 init 初始化 / running 运行中 / completed 完成 / abnormal 异常
   * @property {string}           step                                - 当前步骤 / trying / confirming / cancelling
   * @property {object}           log                                 - 日志信息
   * @property {object}           payload                             - 数据载体
   * @property {Array.<number>}   tryIds                              - TryAction 的 id集合
   * @property {Array.<number>}   confirmIds                          - Confirm 关联 id集合
   * @property {Array.<number>}   cancelIds                           - Cancel 关联 id集合
   * @property {boolean}          tryAllCompleted                     - TRY行为是否全部通过
   * @property {boolean}          confirmAllCompleted                 - Confirm行为是否全部通过
   * @property {boolean}          cancelAllCompleted                  - Cancel行为是否全部通过
   * @property {Array.<object>}   tryActionsInfo                      - TRY详细进度
   * @property {number}           tryActionsInfo.id                   - tryAction 的id
   * @property {boolean}          tryActionsInfo.success              - tryAction 是否执行成功
   * @property {number}           tryActionsInfo.maxAttemptTime       - tryAction 的最大允许执行次数
   * @property {number}           tryActionsInfo.currentAttemptTime   - tryAction 的当前执行次数
   * @property {Array.<object>}   confirmActionsInfo                  - Confirm详细进度
   * @property {Array.<object>}   cancelActionsInfo                   - Cancel详细进度
   * @property {number}           spacingMilliSeconds                 - 间隔毫秒,失败后再次执行的间隔
   *
   */
  TransactionInstance.init({
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true,
    },
    title: {
      type: DataTypes.STRING(30),
      allowNull: false,
      comment: '标题标题',
    },
    messageId: {
      type: DataTypes.STRING(225),
      allowNull: false,
      comment: '订单id',
    },
    projectId: {
      type: DataTypes.INTEGER,
      allowNull: false,
      comment: '项目id',
    },
    proccessId: {
      type: DataTypes.INTEGER,
      allowNull: false,
      comment: '进度id',
    },
    desc: {
      type: DataTypes.TEXT,
      comment: '流程描述',
    },
    status: {
      type: DataTypes.ENUM,
      allowNull: true,
      defaultValue: STATUS_INIT,
      values: [STATUS_INIT, STATUS_RUNNING, STATUS_COMPLETED, STATUS_ABNORMAL],
      comment: `状态: ${STATUS_INIT} 初始化; ${STATUS_RUNNING} 运行中; ${STATUS_COMPLETED}完成; ${STATUS_ABNORMAL} 异常`,
    },
    step: {
      type: DataTypes.ENUM,
      allowNull: true,
      defaultValue: '',
      values: ['', STEP_TRYING, STEP_CONFIRMING, STEP_CANCELLING],
      comment: '步骤',
    },
    log: {
      type: DataTypes.JSON,
      comment: '日志',
    },
    payload: {
      type: DataTypes.JSON,
      comment: '数据载体',
    },
    // tcc
    tryIds: {
      type: DataTypes.JSON,
      defaultValue: [],
      comment: 'Try 关联 id集合',
    },
    confirmIds: {
      type: DataTypes.JSON,
      defaultValue: [],
      comment: 'Confirm 关联 id集合',
    },
    cancelIds: {
      type: DataTypes.JSON,
      defaultValue: [],
      comment: 'Cancel 关联 id集合',
    },
    tryAllCompleted: {
      type: DataTypes.BOOLEAN,
      allowNull: false,
      defaultValue: false,
      comment: 'TRY 行为全部通过',
    },
    confirmAllCompleted: {
      type: DataTypes.BOOLEAN,
      allowNull: false,
      defaultValue: false,
      comment: 'Confirm 行为全部通过',
    },
    cancelAllCompleted: {
      type: DataTypes.BOOLEAN,
      allowNull: false,
      defaultValue: false,
      comment: 'Cancel 行为全部通过',
    },
    tryActionsInfo: {
      type: DataTypes.JSON,
      defaultValue: [],
      comment: 'TRY 详细进度',
    },
    confirmActionsInfo: {
      type: DataTypes.JSON,
      defaultValue: [],
      comment: 'Confirm 详细进度',
    },
    cancelActionsInfo: {
      type: DataTypes.JSON,
      defaultValue: [],
      comment: 'Cancel 详细进度',
    },
    spacingMilliSeconds: {
      type: DataTypes.INTEGER,
      allowNull: false,
      defaultValue: 1000,
      comment: '间隔毫秒',
    },
    createdAt: DataTypes.DATE,
    updatedAt: DataTypes.DATE,
    version: DataTypes.INTEGER,
  }, {
    timestamps: true,
    paranoid: false,
    version: true,
    freezeTableName: true,
    // define the table's name
    tableName: 'transaction-instances',
    modelName: 'TransactionInstance',
    sequelize,
    indexes: [
      { unique: true, fields: ['messageId', 'proccessId'] },
    ],
    getterMethods: {
      tryActionsInfo: getterActionsInfo('tryActionsInfo', 'tryIds'),
      confirmActionsInfo: getterActionsInfo('confirmActionsInfo', 'confirmIds'),
      cancelActionsInfo: getterActionsInfo('cancelActionsInfo', 'cancelIds'),
    },
    setterMethods: {
      tryActionsInfo: setterActionsInfo('tryActionsInfo'),
      confirmActionsInfo: setterActionsInfo('confirmActionsInfo'),
      cancelActionsInfo: setterActionsInfo('cancelActionsInfo'),
    },
  });

  return TransactionInstance;
};