聊聊如何設計一款靈活配置、方便管理的工單系統

簡單聊聊

在咱們平時的工做中,會有不少時候須要跟其餘同事進行協同辦公,甚至須要跨部門合做,在這種狀況下,咱們就須要同事之間的溝通交流,而溝經過程中一句不合適的話,都會形成爭吵,跨部門合做的時候,更加麻煩,由於對彼此都不熟悉,所以很難進行很好的溝通交流。git

其實不少時候,咱們的工做都是流程化,上一位同事處理完,下一位同事接着進行處理,在這種場景需求下就誕生了工單系統,將一個任務處理過程當中,所涉及到的人,按照優先級排列,而後依次進行處理,處理過程當中可進行註解,方便下一個處理人對以前處理結果的熟悉,而且處理完成後經過郵件或者其餘通信方式通知下一我的處理,依次類推,當處理完成後,通知工單申請人,工單已所有處理完成。github

其實目前有一些現成的工單系統了,有開源的也有收費的,可是廣泛的功能比較單一,不方便管理,固然確定有我不知道的很是流弊的工單系統,只是在說我已知的,若是冒犯到你的話,深感抱歉。sql

本篇文章就給你們詳細的介紹一下如何設計一個靈活,易維護管理的工單系統,在介紹此工單系統以前,首先給你們推薦一個仍是不錯的工單系統,供你們參考,學習。數據庫

Demo: ferry 工單系統(點擊進入演示Demo)json

Github: https://github.com/lanyulei/ferry服務器

Gitee: https://gitee.com/yllan/ferry數據結構

核心功能介紹

單點登錄

目前大部分公司都會安裝部署一套單點登陸系統,方便維護管理員工的帳號信息,同時實現多系統的用戶登錄,例如Ldap,所以支持主流的單點登陸系統也是頗有必要的。app

權限管理

對於一個工單系統來講,由於涉及的人會很是多,審批階段會有各層次的領導進行審閱;處理階段會有各部門的人進行處理。所以對於權限的管理是要很是的嚴格。數據庫設計

流程設計

在工單系統中流程設計應該算是最繁瑣的地方了,要定義好每一個節點的關聯關係、處理人以及各類屬性數據,若是沒有一個好的流程設計工具,對於工單系統的用戶體驗來講,是很是差的。並且對於後續的維護也是很是的不易。tcp

以前就有朋友說,他們的工單系統是寫死的,每一個流程,每一個節點,每一個處理人都是在代碼裏定義好的,這樣的工單系統是很是不易用的,只要有人員變更,流程變更,都須要從新修改代碼進行編譯上線,所以好的流程設計工具,對一個好的工單系統來講,是相當重要的。

模版設計

咱們在提交工單的時候,須要寫不少的描述數據,例如申請服務器,就須要寫服務器的配置信息,像是CPU、內存、磁盤等信息,這些須要寫的描述數據,並不是是一成不變的,會根據場景的不一樣而進行適當的修改,所以咱們就須要有一個可視化的表單設計工具,來進行表單模版的維護管理,經過將表單模版與流程的關聯從而實現一條完整的工做流。

多樣化的工單列表

咱們工單系統內的工單,根據工單歸屬的分類,能夠大概的分爲:我建立的工單、我待辦的工單、我相關的工單以及全部工單。咱們根據這4類工單歸屬類型進行數據的分類展現,方便他人的使用及查看。

任務管理

在一個工單流程中,一般不少操做都是能夠自動完成的,好比申請數據庫帳號,可讓用戶填寫好數據後,DBA審批經過,則自動去建立帳號,並進行郵件或者其餘通信工具進行通知。這種就能節省不少人爲操做,使這我的能夠處理更多其餘的工做。畢竟20世紀最貴的成本是人力呢。

更多其餘功能點

  • 加簽: 臨時將工單轉交給他人處理,處理完成後自動返回給原來的處理人。
  • 會籤: 當一個節點出現多個處理人的時候,則須要全部的處理人都處理完成纔可進行下一步。
  • 催辦: 催促當前節點的處理人儘快處理,固然須要設置時間間隔的。
  • 結單: 有的時候咱們會提錯單子,由於可聯繫管理員進行手動結單。
  • 轉交: 在流程中,某一節點的處理人臨時有時間,商議後,可轉交給他人。
  • 主動接單: 當一個節點出現多個處理人的時候,配置主動接單後,需這幾我的進行搶單處理。
  • 等等,還有不少擴展的功能,有興趣的人,能夠加羣一塊兒聊聊:1127401830 。

以上就是大概總結的一些核心的功能,固然還有不少細小的劃分。更精細的設計就須要你們去認真構思了。畢竟一篇文章若是說考慮到方方面面的話,那是不現實的,得須要碼多少字呢。

數據庫設計

其實開發一個系統,最耗費時間的不是敲代碼的部分。程序設計,數據結構設計纔是最繁瑣的,要考慮到各類場景、各類關聯。

因此這裏就不藏私了,直接給你們看下個人數據結構設計:

用戶/權限/菜單/系統配置

image

工單系統

image

核心功能代碼

在此將核心功能中代碼給你們展現一下,可是確定不會所有的(由於實在是太多了,貼不完呢),有興趣的能夠去Github上Clone下來詳細的研究一下。

Github: https://github.com/lanyulei/ferry

Gitee: https://gitee.com/yllan/ferry

Ldap登錄驗證

ldap算是比較經常使用的一個單點登錄系統了,跟同事、同窗、朋友聊天的過程當中,能感受大部分都是在使用ldap。固然有人會說ldap是一個協議,這些就不用糾結這些細節了,你也能夠說,使用了ldap協議單點登陸系統。

集成ldap登錄驗證後,在用戶驗證經過會,會將數據保存至本地數據庫一份,方便維護管理。

鏈接ldap:

package ldap

import (
    "crypto/tls"
    "errors"
    "ferry/pkg/logger"
    "fmt"
    "time"

    "github.com/spf13/viper"

    "github.com/go-ldap/ldap/v3"
)

/*
  @Author : lanyulei
*/

var conn *ldap.Conn

// ldap鏈接
func ldapConnection() (err error) {
    var ldapConn = fmt.Sprintf("%v:%v", viper.GetString("settings.ldap.host"), viper.GetString("settings.ldap.port"))

    conn, err = ldap.Dial(
        "tcp",
        ldapConn,
    )
    if err != nil {
        err = errors.New(fmt.Sprintf("沒法鏈接到ldap服務器,%v", err))
        logger.Error(err)
        return
    }

    if viper.GetBool("settings.ldap.tls") {
        err = conn.StartTLS(&tls.Config{
            InsecureSkipVerify: true,
        })
        if err != nil {
            err = errors.New(fmt.Sprintf("升級到加密方式失敗,%v", err))
            logger.Error(err)
            return
        }
    }

    //設置超時時間
    conn.SetTimeout(5 * time.Second)

    return
}

搜索ldap用戶信息:

package ldap

import (
    "encoding/json"
    "errors"
    "ferry/global/orm"
    "ferry/models/system"
    "ferry/pkg/logger"
    "fmt"

    "github.com/go-ldap/ldap/v3"
    "github.com/spf13/viper"
)

/*
  @Author : lanyulei
*/

func getLdapFields() (ldapFields []map[string]string, err error) {
    var (
        settingsValue system.Settings
        contentList   []map[string]string
    )

    err = orm.Eloquent.Model(&settingsValue).Where("classify = 2").Find(&settingsValue).Error
    if err != nil {
        return
    }

    err = json.Unmarshal(settingsValue.Content, &contentList)
    if err != nil {
        return
    }

    for _, v := range contentList {
        if v["ldap_field_name"] != "" {
            ldapFields = append(ldapFields, v)
        }
    }
    return
}

func searchRequest(username string) (userInfo *ldap.Entry, err error) {
    var (
        ldapFields       []map[string]string
        cur              *ldap.SearchResult
        ldapFieldsFilter = []string{
            "dn",
        }
    )
    ldapFields, err = getLdapFields()
    for _, v := range ldapFields {
        ldapFieldsFilter = append(ldapFieldsFilter, v["ldap_field_name"])
    }
    // 用來獲取查詢權限的用戶。若是 ldap 禁止了匿名查詢,那咱們就須要先用這個賬戶 bind 如下才能開始查詢
    if !viper.GetBool("settings.ldap.anonymousQuery") {
        err = conn.Bind(
            fmt.Sprintf("cn=%v,%v",
                viper.GetString("settings.ldap.bindUser"),
                viper.GetString("settings.ldap.baseDn")),
            viper.GetString("settings.ldap.bindPwd"))
        if err != nil {
            logger.Error("用戶或密碼錯誤。", err)
            return
        }
    }

    sql := ldap.NewSearchRequest(
        viper.GetString("settings.ldap.baseDn"),
        ldap.ScopeWholeSubtree,
        ldap.DerefAlways,
        0,
        0,
        false,
        fmt.Sprintf("(cn=%s)", username),
        ldapFieldsFilter,
        nil)

    if cur, err = conn.Search(sql); err != nil {
        err = errors.New(fmt.Sprintf("在Ldap搜索用戶失敗, %v", err))
        logger.Error(err)
        return
    }

    if len(cur.Entries) == 0 {
        err = errors.New("未查詢到對應的用戶信息。")
        logger.Error(err)
        return
    }

    userInfo = cur.Entries[0]

    return
}

映射本地數據庫字段:

package ldap

import (
    "ferry/models/system"

    "github.com/go-ldap/ldap/v3"
)

/*
  @Author : lanyulei
*/

func LdapFieldsMap(ldapUserInfo *ldap.Entry) (userInfo system.SysUser, err error) {
    var (
        ldapFields []map[string]string
    )

    ldapFields, err = getLdapFields()
    if err != nil {
        return
    }

    for _, v := range ldapFields {
        switch v["local_field_name"] {
        case "nick_name":
            userInfo.NickName = ldapUserInfo.GetAttributeValue(v["ldap_field_name"])
        case "phone":
            userInfo.Phone = ldapUserInfo.GetAttributeValue(v["ldap_field_name"])
        case "avatar":
            userInfo.Avatar = ldapUserInfo.GetAttributeValue(v["ldap_field_name"])
        case "sex":
            userInfo.Sex = ldapUserInfo.GetAttributeValue(v["ldap_field_name"])
        case "email":
            userInfo.Email = ldapUserInfo.GetAttributeValue(v["ldap_field_name"])
        case "remark":
            userInfo.Remark = ldapUserInfo.GetAttributeValue(v["ldap_field_name"])
        }
    }

    return
}

ldap用戶登錄:

package ldap

import (
    "fmt"

    "github.com/go-ldap/ldap/v3"
)

/*
  @Author : lanyulei
*/

func LdapLogin(username string, password string) (userInfo *ldap.Entry, err error) {
    err = ldapConnection()
    if err != nil {
        return
    }
    defer conn.Close()

    userInfo, err = searchRequest(username)
    if err != nil {
        return
    }

    err = conn.Bind(userInfo.DN, password)
    if err != nil {
        return nil, fmt.Errorf("用戶或密碼不正確。")
    }

    return
}

工單流轉

在一個工單系統中,最核心的部分就是工單流轉,要兼容各類處理人信息,而且根據節點之間的關聯進行節點的流轉,所以,在工單流轉這一部分咱們須要很是認真對待,由於若是你不認真對待,可能會形成工單流轉到一個非此流程相關的人名下,對於用戶體驗是很是差的,甚至會喪失用戶對系統的信用再也不使用此係統。因此這段是重中之重。需認真對待。

package service

import (
    "encoding/json"
    "errors"
    "ferry/global/orm"
    "ferry/models/base"
    "ferry/models/process"
    "ferry/models/system"
    "ferry/pkg/notify"
    "ferry/tools"
    "fmt"
    "reflect"
    "time"

    "github.com/jinzhu/gorm"

    "github.com/gin-gonic/gin"
)

/*
  @Author : lanyulei
  @Desc : 處理工單
*/

/*
    -- 節點 --
    start: 開始節點
    userTask: 審批節點
    receiveTask: 處理節點
    scriptTask: 任務節點
    end: 結束節點
    -- 網關 --
    exclusiveGateway: 排他網關
    parallelGateway: 並行網關
    inclusiveGateway: 包容網關
*/

type Handle struct {
    cirHistoryList   []process.CirculationHistory
    workOrderId      int
    updateValue      map[string]interface{}
    stateValue       map[string]interface{}
    targetStateValue map[string]interface{}
    workOrderData    [][]byte
    workOrderDetails process.WorkOrderInfo
    endHistory       bool
    flowProperties   int
    circulationValue string
    processState     ProcessState
    tx               *gorm.DB
}

// 時間格式化
func fmtDuration(d time.Duration) string {
    d = d.Round(time.Minute)
    h := d / time.Hour
    d -= h * time.Hour
    m := d / time.Minute
    return fmt.Sprintf("%02d小時 %02d分鐘", h, m)
}

// 會籤
func (h *Handle) Countersign(c *gin.Context) (err error) {
    var (
        stateList       []map[string]interface{}
        stateIdMap      map[string]interface{}
        currentState    map[string]interface{}
        cirHistoryCount int
    )

    err = json.Unmarshal(h.workOrderDetails.State, &stateList)
    if err != nil {
        return
    }

    stateIdMap = make(map[string]interface{})
    for _, v := range stateList {
        stateIdMap[v["id"].(string)] = v["label"]
        if v["id"].(string) == h.stateValue["id"].(string) {
            currentState = v
        }
    }
    for _, cirHistoryValue := range h.cirHistoryList {
        if _, ok := stateIdMap[cirHistoryValue.Source]; !ok {
            break
        }
        for _, processor := range currentState["processor"].([]interface{}) {
            if cirHistoryValue.ProcessorId != tools.GetUserId(c) &&
                cirHistoryValue.Source == currentState["id"].(string) &&
                cirHistoryValue.ProcessorId == int(processor.(float64)) {
                cirHistoryCount += 1
            }
        }
    }
    if cirHistoryCount == len(currentState["processor"].([]interface{}))-1 {
        h.endHistory = true
        err = h.circulation()
        if err != nil {
            return
        }
    }
    return
}

// 工單跳轉
func (h *Handle) circulation() (err error) {
    var (
        stateValue []byte
    )

    stateList := make([]interface{}, 0)
    for _, v := range h.updateValue["state"].([]map[string]interface{}) {
        stateList = append(stateList, v)
    }
    err = GetVariableValue(stateList, h.workOrderDetails.Creator)
    if err != nil {
        return
    }

    stateValue, err = json.Marshal(h.updateValue["state"])
    if err != nil {
        return
    }

    err = h.tx.Model(&process.WorkOrderInfo{}).
        Where("id = ?", h.workOrderId).
        Updates(map[string]interface{}{
            "state":          stateValue,
            "related_person": h.updateValue["related_person"],
        }).Error
    if err != nil {
        h.tx.Rollback()
        return
    }
    return
}

// 條件判斷
func (h *Handle) ConditionalJudgment(condExpr map[string]interface{}) (result bool, err error) {
    var (
        condExprOk    bool
        condExprValue interface{}
    )

    defer func() {
        if r := recover(); r != nil {
            switch e := r.(type) {
            case string:
                err = errors.New(e)
            case error:
                err = e
            default:
                err = errors.New("未知錯誤")
            }
            return
        }
    }()

    for _, data := range h.workOrderData {
        var formData map[string]interface{}
        err = json.Unmarshal(data, &formData)
        if err != nil {
            return
        }
        if condExprValue, condExprOk = formData[condExpr["key"].(string)]; condExprOk {
            break
        }
    }

    if condExprValue == nil {
        err = errors.New("未查詢到對應的表單數據。")
        return
    }

    // todo 待優化
    switch reflect.TypeOf(condExprValue).String() {
    case "string":
        switch condExpr["sign"] {
        case "==":
            if condExprValue.(string) == condExpr["value"].(string) {
                result = true
            }
        case "!=":
            if condExprValue.(string) != condExpr["value"].(string) {
                result = true
            }
        case ">":
            if condExprValue.(string) > condExpr["value"].(string) {
                result = true
            }
        case ">=":
            if condExprValue.(string) >= condExpr["value"].(string) {
                result = true
            }
        case "<":
            if condExprValue.(string) < condExpr["value"].(string) {
                result = true
            }
        case "<=":
            if condExprValue.(string) <= condExpr["value"].(string) {
                result = true
            }
        default:
            err = errors.New("目前僅支持6種常規判斷類型,包括(等於、不等於、大於、大於等於、小於、小於等於)")
        }
    default:
        err = errors.New("條件判斷目前僅支持字符串、整型。")
    }

    return
}

// 並行網關,確認其餘節點是否完成
func (h *Handle) completeAllParallel(c *gin.Context, target string) (statusOk bool, err error) {
    var (
        stateList []map[string]interface{}
    )

    err = json.Unmarshal(h.workOrderDetails.State, &stateList)
    if err != nil {
        err = fmt.Errorf("反序列化失敗,%v", err.Error())
        return
    }

continueHistoryTag:
    for _, v := range h.cirHistoryList {
        status := false
        for i, s := range stateList {
            if v.Source == s["id"].(string) && v.Target == target {
                status = true
                stateList = append(stateList[:i], stateList[i+1:]...)
                continue continueHistoryTag
            }
        }
        if !status {
            break
        }
    }

    if len(stateList) == 1 && stateList[0]["id"].(string) == h.stateValue["id"] {
        statusOk = true
    }

    return
}

func (h *Handle) commonProcessing(c *gin.Context) (err error) {
    // 若是是拒絕的流轉則直接跳轉
    if h.flowProperties == 0 {
        err = h.circulation()
        if err != nil {
            err = fmt.Errorf("工單跳轉失敗,%v", err.Error())
        }
        return
    }

    // 會籤
    if h.stateValue["assignValue"] != nil && len(h.stateValue["assignValue"].([]interface{})) > 1 {
        if isCounterSign, ok := h.stateValue["isCounterSign"]; ok {
            if isCounterSign.(bool) {
                h.endHistory = false
                err = h.Countersign(c)
                if err != nil {
                    return
                }
            } else {
                err = h.circulation()
                if err != nil {
                    return
                }
            }
        } else {
            err = h.circulation()
            if err != nil {
                return
            }
        }
    } else {
        err = h.circulation()
        if err != nil {
            return
        }
    }
    return
}

func (h *Handle) HandleWorkOrder(
    c *gin.Context,
    workOrderId int,
    tasks []string,
    targetState string,
    sourceState string,
    circulationValue string,
    flowProperties int,
    remarks string,
) (err error) {
    h.workOrderId = workOrderId
    h.flowProperties = flowProperties
    h.endHistory = true

    var (
        execTasks          []string
        relatedPersonList  []int
        cirHistoryValue    []process.CirculationHistory
        cirHistoryData     process.CirculationHistory
        costDurationValue  string
        sourceEdges        []map[string]interface{}
        targetEdges        []map[string]interface{}
        condExprStatus     bool
        relatedPersonValue []byte
        parallelStatusOk   bool
        processInfo        process.Info
        currentUserInfo    system.SysUser
        sendToUserList     []system.SysUser
        noticeList         []int
        sendSubject        string = "您有一條待辦工單,請及時處理"
        sendDescription    string = "您有一條待辦工單請及時處理,工單描述以下"
    )

    defer func() {
        if r := recover(); r != nil {
            switch e := r.(type) {
            case string:
                err = errors.New(e)
            case error:
                err = e
            default:
                err = errors.New("未知錯誤")
            }
            return
        }
    }()

    // 獲取工單信息
    err = orm.Eloquent.Model(&process.WorkOrderInfo{}).Where("id = ?", workOrderId).Find(&h.workOrderDetails).Error
    if err != nil {
        return
    }

    // 獲取流程信息
    err = orm.Eloquent.Model(&process.Info{}).Where("id = ?", h.workOrderDetails.Process).Find(&processInfo).Error
    if err != nil {
        return
    }
    err = json.Unmarshal(processInfo.Structure, &h.processState.Structure)
    if err != nil {
        return
    }

    // 獲取當前節點
    h.stateValue, err = h.processState.GetNode(sourceState)
    if err != nil {
        return
    }

    // 目標狀態
    h.targetStateValue, err = h.processState.GetNode(targetState)
    if err != nil {
        return
    }

    // 獲取工單數據
    err = orm.Eloquent.Model(&process.TplData{}).
        Where("work_order = ?", workOrderId).
        Pluck("form_data", &h.workOrderData).Error
    if err != nil {
        return
    }

    // 根據處理人查詢出須要會籤的條數
    err = orm.Eloquent.Model(&process.CirculationHistory{}).
        Where("work_order = ?", workOrderId).
        Order("id desc").
        Find(&h.cirHistoryList).Error
    if err != nil {
        return
    }

    err = json.Unmarshal(h.workOrderDetails.RelatedPerson, &relatedPersonList)
    if err != nil {
        return
    }
    relatedPersonStatus := false
    for _, r := range relatedPersonList {
        if r == tools.GetUserId(c) {
            relatedPersonStatus = true
            break
        }
    }
    if !relatedPersonStatus {
        relatedPersonList = append(relatedPersonList, tools.GetUserId(c))
    }

    relatedPersonValue, err = json.Marshal(relatedPersonList)
    if err != nil {
        return
    }

    h.updateValue = map[string]interface{}{
        "related_person": relatedPersonValue,
    }

    // 開啓事務
    h.tx = orm.Eloquent.Begin()

    stateValue := map[string]interface{}{
        "label": h.targetStateValue["label"].(string),
        "id":    h.targetStateValue["id"].(string),
    }

    switch h.targetStateValue["clazz"] {
    // 排他網關
    case "exclusiveGateway":
        sourceEdges, err = h.processState.GetEdge(h.targetStateValue["id"].(string), "source")
        if err != nil {
            return
        }
    breakTag:
        for _, edge := range sourceEdges {
            edgeCondExpr := make([]map[string]interface{}, 0)
            err = json.Unmarshal([]byte(edge["conditionExpression"].(string)), &edgeCondExpr)
            if err != nil {
                return
            }
            for _, condExpr := range edgeCondExpr {
                // 條件判斷
                condExprStatus, err = h.ConditionalJudgment(condExpr)
                if err != nil {
                    return
                }
                if condExprStatus {
                    // 進行節點跳轉
                    h.targetStateValue, err = h.processState.GetNode(edge["target"].(string))
                    if err != nil {
                        return
                    }

                    if h.targetStateValue["clazz"] == "userTask" || h.targetStateValue["clazz"] == "receiveTask" {
                        if h.targetStateValue["assignValue"] == nil || h.targetStateValue["assignType"] == "" {
                            err = errors.New("處理人不能爲空")
                            return
                        }
                    }

                    h.updateValue["state"] = []map[string]interface{}{{
                        "id":             h.targetStateValue["id"].(string),
                        "label":          h.targetStateValue["label"],
                        "processor":      h.targetStateValue["assignValue"],
                        "process_method": h.targetStateValue["assignType"],
                    }}
                    err = h.commonProcessing(c)
                    if err != nil {
                        err = fmt.Errorf("流程流程跳轉失敗,%v", err.Error())
                        return
                    }

                    break breakTag
                }
            }
        }
        if !condExprStatus {
            err = errors.New("全部流轉均不符合條件,請確認。")
            return
        }
    // 並行/聚合網關
    case "parallelGateway":
        // 入口,判斷
        sourceEdges, err = h.processState.GetEdge(h.targetStateValue["id"].(string), "source")
        if err != nil {
            err = fmt.Errorf("查詢流轉信息失敗,%v", err.Error())
            return
        }

        targetEdges, err = h.processState.GetEdge(h.targetStateValue["id"].(string), "target")
        if err != nil {
            err = fmt.Errorf("查詢流轉信息失敗,%v", err.Error())
            return
        }

        if len(sourceEdges) > 0 {
            h.targetStateValue, err = h.processState.GetNode(sourceEdges[0]["target"].(string))
            if err != nil {
                return
            }
        } else {
            err = errors.New("並行網關流程不正確")
            return
        }

        if len(sourceEdges) > 1 && len(targetEdges) == 1 {
            // 入口
            h.updateValue["state"] = make([]map[string]interface{}, 0)
            for _, edge := range sourceEdges {
                targetStateValue, err := h.processState.GetNode(edge["target"].(string))
                if err != nil {
                    return err
                }
                h.updateValue["state"] = append(h.updateValue["state"].([]map[string]interface{}), map[string]interface{}{
                    "id":             edge["target"].(string),
                    "label":          targetStateValue["label"],
                    "processor":      targetStateValue["assignValue"],
                    "process_method": targetStateValue["assignType"],
                })
            }
            err = h.circulation()
            if err != nil {
                err = fmt.Errorf("工單跳轉失敗,%v", err.Error())
                return
            }
        } else if len(sourceEdges) == 1 && len(targetEdges) > 1 {
            // 出口
            parallelStatusOk, err = h.completeAllParallel(c, sourceEdges[0]["target"].(string))
            if err != nil {
                err = fmt.Errorf("並行檢測失敗,%v", err.Error())
                return
            }
            if parallelStatusOk {
                h.endHistory = true
                endAssignValue, ok := h.targetStateValue["assignValue"]
                if !ok {
                    endAssignValue = []int{}
                }

                endAssignType, ok := h.targetStateValue["assignType"]
                if !ok {
                    endAssignType = ""
                }

                h.updateValue["state"] = []map[string]interface{}{{
                    "id":             h.targetStateValue["id"].(string),
                    "label":          h.targetStateValue["label"],
                    "processor":      endAssignValue,
                    "process_method": endAssignType,
                }}
                err = h.circulation()
                if err != nil {
                    err = fmt.Errorf("工單跳轉失敗,%v", err.Error())
                    return
                }
            } else {
                h.endHistory = false
            }

        } else {
            err = errors.New("並行網關流程不正確")
            return
        }
    // 包容網關
    case "inclusiveGateway":
        return
    case "start":
        stateValue["processor"] = []int{h.workOrderDetails.Creator}
        stateValue["process_method"] = "person"
        h.updateValue["state"] = []map[string]interface{}{stateValue}
        err = h.circulation()
        if err != nil {
            return
        }
    case "userTask":
        stateValue["processor"] = h.targetStateValue["assignValue"].([]interface{})
        stateValue["process_method"] = h.targetStateValue["assignType"].(string)
        h.updateValue["state"] = []map[string]interface{}{stateValue}
        err = h.commonProcessing(c)
        if err != nil {
            return
        }
    case "receiveTask":
        stateValue["processor"] = h.targetStateValue["assignValue"].([]interface{})
        stateValue["process_method"] = h.targetStateValue["assignType"].(string)
        h.updateValue["state"] = []map[string]interface{}{stateValue}
        err = h.commonProcessing(c)
        if err != nil {
            return
        }
    case "scriptTask":
        stateValue["processor"] = []int{}
        stateValue["process_method"] = ""
        h.updateValue["state"] = []map[string]interface{}{stateValue}
    case "end":
        stateValue["processor"] = []int{}
        stateValue["process_method"] = ""
        h.updateValue["state"] = []map[string]interface{}{stateValue}
        err = h.circulation()
        if err != nil {
            h.tx.Rollback()
            return
        }
        err = h.tx.Model(&process.WorkOrderInfo{}).
            Where("id = ?", h.workOrderId).
            Update("is_end", 1).Error
        if err != nil {
            h.tx.Rollback()
            return
        }
    }

    // 流轉歷史寫入
    err = orm.Eloquent.Model(&cirHistoryValue).
        Where("work_order = ?", workOrderId).
        Find(&cirHistoryValue).
        Order("create_time desc").Error
    if err != nil {
        h.tx.Rollback()
        return
    }
    for _, t := range cirHistoryValue {
        if t.Source != h.stateValue["id"] {
            costDuration := time.Since(t.CreatedAt.Time)
            costDurationValue = fmtDuration(costDuration)
        }
    }

    // 獲取當前用戶信息
    err = orm.Eloquent.Model(¤tUserInfo).
        Where("user_id = ?", tools.GetUserId(c)).
        Find(¤tUserInfo).Error
    if err != nil {
        return
    }

    cirHistoryData = process.CirculationHistory{
        Model:        base.Model{},
        Title:        h.workOrderDetails.Title,
        WorkOrder:    h.workOrderDetails.Id,
        State:        h.stateValue["label"].(string),
        Source:       h.stateValue["id"].(string),
        Target:       h.targetStateValue["id"].(string),
        Circulation:  circulationValue,
        Processor:    currentUserInfo.NickName,
        ProcessorId:  tools.GetUserId(c),
        CostDuration: costDurationValue,
        Remarks:      remarks,
    }

    err = h.tx.Create(&cirHistoryData).Error
    if err != nil {
        h.tx.Rollback()
        return
    }

    // 獲取流程通知類型列表
    err = json.Unmarshal(processInfo.Notice, ¬iceList)
    if err != nil {
        return
    }

    bodyData := notify.BodyData{
        SendTo: map[string]interface{}{
            "userList": sendToUserList,
        },
        Subject:     sendSubject,
        Description: sendDescription,
        Classify:    noticeList,
        ProcessId:   h.workOrderDetails.Process,
        Id:          h.workOrderDetails.Id,
        Title:       h.workOrderDetails.Title,
        Creator:     currentUserInfo.NickName,
        Priority:    h.workOrderDetails.Priority,
        CreatedAt:   h.workOrderDetails.CreatedAt.Format("2006-01-02 15:04:05"),
    }

    // 判斷目標是不是結束節點
    if h.targetStateValue["clazz"] == "end" && h.endHistory == true {
        sendSubject = "您的工單已處理完成"
        sendDescription = "您的工單已處理完成,工單描述以下"
        err = h.tx.Create(&process.CirculationHistory{
            Model:       base.Model{},
            Title:       h.workOrderDetails.Title,
            WorkOrder:   h.workOrderDetails.Id,
            State:       h.targetStateValue["label"].(string),
            Source:      h.targetStateValue["id"].(string),
            Processor:   currentUserInfo.NickName,
            ProcessorId: tools.GetUserId(c),
            Circulation: "結束",
            Remarks:     "工單已結束",
        }).Error
        if err != nil {
            h.tx.Rollback()
            return
        }
        if len(noticeList) > 0 {
            // 查詢工單建立人信息
            err = h.tx.Model(&system.SysUser{}).
                Where("user_id = ?", h.workOrderDetails.Creator).
                Find(&sendToUserList).Error
            if err != nil {
                return
            }

            bodyData.SendTo = map[string]interface{}{
                "userList": sendToUserList,
            }
            bodyData.Subject = sendSubject
            bodyData.Description = sendDescription

            // 發送通知
            go func(bodyData notify.BodyData) {
                err = bodyData.SendNotify()
                if err != nil {
                    return
                }
            }(bodyData)
        }
    }

    h.tx.Commit() // 提交事務

    // 發送通知
    if len(noticeList) > 0 {
        stateList := make([]interface{}, 0)
        for _, v := range h.updateValue["state"].([]map[string]interface{}) {
            stateList = append(stateList, v)
        }
        sendToUserList, err = GetPrincipalUserInfo(stateList, h.workOrderDetails.Creator)
        if err != nil {
            return
        }

        bodyData.SendTo = map[string]interface{}{
            "userList": sendToUserList,
        }
        bodyData.Subject = sendSubject
        bodyData.Description = sendDescription

        // 發送通知
        go func(bodyData notify.BodyData) {
            err = bodyData.SendNotify()
            if err != nil {
                return
            }
        }(bodyData)
    }

    // 執行流程公共任務及節點任務
    if h.stateValue["task"] != nil {
        for _, task := range h.stateValue["task"].([]interface{}) {
            tasks = append(tasks, task.(string))
        }
    }
continueTag:
    for _, task := range tasks {
        for _, t := range execTasks {
            if t == task {
                continue continueTag
            }
        }
        execTasks = append(execTasks, task)
    }
    go ExecTask(execTasks)

    return
}

ok, anyway.

加上一段在公司常常聽到英文做爲結束語,若是能實現本文的這些功能,其實一個簡單易用,靈活方便維護管理的工單系統就成型了。

在此給你們說聲抱歉,沒法將完整的代碼貼在這裏(由於實在是太多了),若是你們還有什麼疑問的話,歡迎隨時來個人博客留言,我看到會盡快回復,也能夠加入個人qq羣,一塊兒討論學習。

blog: 蘭玉磊的技術博客

QQ羣:1127401830

相關文章
相關標籤/搜索