package utils import ( "context" "errors" "github.com/gogf/gf/database/gdb" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gmeta" "youngmini_server/app/dao" "youngmini_server/app/model" ) var OrderProcedureManager = orderProcedureManager{} type orderProcedureManager struct { } // MethodType 拍单方式 type MethodType int const ( MethodTypeNo MethodType = iota MethodTypeYes MethodTypeOther ) type buySamplesType int const ( buySamplesTypeNo buySamplesType = iota + 1 ) type procedureStage int // 订单执行阶段 const ( procedureStageWaitForSelect procedureStage = iota + 1 // 等待反选阶段 procedureStageBuySamples // 拍单阶段 procedureStageDraft // 初稿阶段 procedureStageArticle // 作品阶段 procedureStageLinkTest // 链接质检阶段 procedureStageDataTest // 数据质检阶段 procedureStageReturnSamples // 拍单样品寄回阶段 procedureStageComplete // 订单完成阶段 ) type procedureState int const ( procedureStateNotStart procedureState = iota + 1 orderStepStateGoingOn orderStepStateFail orderStepStateSuc ) // DeductReasonOrderOvertime 扣费原因超期扣费 const DeductReasonOrderOvertime = 1 // 下面的值会出现在"suc_next_step"或"fail_next_step"字段,表示当前步骤成功或失败后进入的下个步骤 // 254 表示任务终止 const orderFinishCode uint = 254 // 255 表示进入订单的下一个stage const nextStageCode = 255 // 0 表示保持在本步骤不动 const selfStepCode = 0 type OrderLimitAndReduceFeeInfo struct { gmeta.Meta `orm:"table:task_base_info"` TaskId int `json:"task_id"` DraftInfo *model.TaskProcedureDraftInfo `orm:"with:task_base_id=task_id"` ArticleInfo *model.TaskProcedureArticleInfo `orm:"with:task_base_id=task_id"` DataAndQualityInfo *model.TaskProcedureQualityDataTestInfo `orm:"with:task_base_id=task_id"` } func (*orderProcedureManager) setLimitDaysInfoForProcedure(node *model.WorkflowNodeContainer, limitInfo *OrderLimitAndReduceFeeInfo, stage int) { if node.ProcedureStage == int(procedureStageDraft) && node.StepInStage == 1 && limitInfo.DraftInfo != nil { // 上传初稿天数限制 node.LimitDays1 = limitInfo.DraftInfo.UploadLimitDay // 上传初稿超期扣费率 node.ReduceFeeRatio1 = limitInfo.DraftInfo.UploadReduceRate // 上传初稿天数限制 node.LimitDays2 = limitInfo.DraftInfo.ReviseLimitDay // 上传初稿超期扣费率 node.ReduceFeeRatio2 = limitInfo.DraftInfo.ReviseReduceRate } if node.ProcedureStage == int(procedureStageArticle) && node.StepInStage == 1 && limitInfo.ArticleInfo != nil { // 上传作品天数限制 node.LimitDays1 = limitInfo.ArticleInfo.UploadLimitDay // 上传作品超期扣费率 node.ReduceFeeRatio1 = limitInfo.ArticleInfo.UploadReduceRate // 上传作品天数限制 node.LimitDays2 = limitInfo.ArticleInfo.ReviseLimitDay // 上传作品超期扣费率 node.ReduceFeeRatio2 = limitInfo.ArticleInfo.ReviseReduceRate } if node.ProcedureStage == int(procedureStageLinkTest) && node.StepInStage == 1 && limitInfo.DataAndQualityInfo != nil { // 上传发布链接天数限制 node.LimitDays1 = limitInfo.DataAndQualityInfo.QualityTestLimitDay // 上传发布链接超期扣费率 node.ReduceFeeRatio1 = limitInfo.DataAndQualityInfo.QualityTestReduceRate // 上传发布链接天数限制 node.LimitDays2 = limitInfo.DataAndQualityInfo.QualityReviseLimitDay // 上传发布链接超期扣费率 node.ReduceFeeRatio2 = limitInfo.DataAndQualityInfo.QualityReviseReduseRate } if node.ProcedureStage == int(procedureStageDataTest) && node.StepInStage == 1 && limitInfo.DataAndQualityInfo != nil { // 上传质检链接天数限制 node.LimitDays1 = limitInfo.DataAndQualityInfo.DataTestLimitDay // 上传质检链接超期扣费率 node.ReduceFeeRatio1 = limitInfo.DataAndQualityInfo.DataTestReduceRate // 上传质检链接天数限制 node.LimitDays2 = limitInfo.DataAndQualityInfo.DataReviceLimitDay // 上传质检链接超期扣费率 node.ReduceFeeRatio2 = limitInfo.DataAndQualityInfo.DataReviceReduceRate } } //func (*orderProcedureManager)calPrevSortId(curSortId int, ) func (t *orderProcedureManager)fillOrderWorkflowToDatabase(taskId, orderId int, stageList []int, ctx context.Context, tx *gdb.TX) error { var orderLimitInfo *OrderLimitAndReduceFeeInfo err := g.DB().Model(dao.TaskBaseInfo.Table).WithAll().Where(dao.TaskBaseInfo.Columns.TaskId, taskId).Scan(&orderLimitInfo) if err != nil {} res, err := g.DB().Model(dao.WorkflowNodeTemplate.Table). OrderAsc("procedure_stage, step_in_stage"). All("procedure_stage in (?)", stageList) if err != nil { return err } workflowNodeList := make([]model.WorkflowNodeContainer, 0) var sucSortId, failSortId, nextStep uint //建立每一个流程的每一步到sortId的索引 sortIdMap := make(map[uint]map[uint]uint, 0) for i := 0; i < res.Len(); i++ { curStage := res[i]["procedure_stage"].Uint() curStepInStage := res[i]["step_in_stage"].Uint() if _, ok := sortIdMap[curStage]; !ok { sortIdMap[curStage] = make(map[uint]uint) } sortIdMap[curStage][curStepInStage] = uint(i + 1) } for i := 0; i < res.Len(); i++ { curStage := res[i]["procedure_stage"].Uint() // 将成功后转到的本stage内的step值替换为完成后的sortId nextStep = res[i]["suc_next_step"].Uint() if nextStep == nextStageCode { // 如果是255则下一步为下一个节点 sucSortId = uint(i + 2) } else if nextStep == orderFinishCode { // 如果下一步是254,则订单直接终止 sucSortId = orderFinishCode } else if nextStep == selfStepCode { // 如果为0则是本节点 sucSortId = uint(i + 1) } else { // 否则查找 v, ok := sortIdMap[curStage][nextStep] if !ok { panic("not found sort id of success next step") } sucSortId = v } // 将失败后转到的本stage内的step值替换为完成后的sortId nextStep = res[i]["fail_next_step"].Uint() if nextStep == nextStageCode { // 如果是255则下一步为下一个节点 failSortId = uint(i + 2) } else if nextStep == orderFinishCode { // 如果下一步是254,则订单直接终止 failSortId = orderFinishCode } else if nextStep == selfStepCode { // 如果为0则是本节点 failSortId = uint(i + 1) } else { // 否则根据已建立的 v, ok := sortIdMap[curStage][nextStep] if !ok { panic("not found sort id of failed next step") } failSortId = v } node := model.WorkflowNodeContainer{ NodeNameFirst: res[i]["node_name_first"].String(), NodeNameAfterSecond: res[i]["node_name_after_second"].String(), ProcedureStage: res[i]["procedure_stage"].Int(), CurExecutionTimes: res[i]["cur_execution_times"].Int(), MaxExecutionTimes: res[i]["max_execution_times"].Int(), StepInStage: res[i]["step_in_stage"].Uint(), Tip: res[i]["tip"].String(), SucNextStep: sucSortId, FailNextStep: failSortId, State: res[i]["state"].Int(), StartDate: gtime.Now(), CompleteDate: nil, CompleteUserName: "", LimitDays1: 0, ReduceFeeRatio1: 0, LimitDays2: 0, ReduceFeeRatio2: 0, OperateRoleRestrict: res[i]["operate_role_restrict"].Int(), SortId: i + 1, OrderId: orderId, } // 上传初稿 上传作品 上传链接 上传数据阶段设置限制天数和扣费信息 t.setLimitDaysInfoForProcedure(&node, orderLimitInfo, node.ProcedureStage) workflowNodeList = append(workflowNodeList, node) } _, err = tx.Ctx(ctx).Model(dao.WorkflowNodeContainer.Table).Insert(workflowNodeList) if err != nil { return err } return nil } // GenOrderWorkflowList 生成订单的流程 func (t *orderProcedureManager) GenOrderWorkflowList(taskId, orderId int, ctx context.Context, tx *gdb.TX) error { r, err := g.DB().Model(model.TaskProcedureDecisionCondition{}).One(dao.TaskProcedureDecisionCondition.Columns.TaskBaseId, taskId) if err != nil { return err } var procedureIdList []int // 添加等待反选步骤,必须添加 procedureIdList = append(procedureIdList, 1) // 如果拍单类型不为“不拍单”,则将拍单对应的步骤加入到流程中 if r[dao.TaskProcedureDecisionCondition.Columns.BuySamplesType].Int() != int(buySamplesTypeNo) { procedureIdList = append(procedureIdList, r[dao.TaskProcedureDecisionCondition.Columns.BuySamplesStage].Int()) } // 如果需要检查初稿则把检查初稿相关步骤加入任务流程 if r[dao.TaskProcedureDecisionCondition.Columns.ExamineDraft].Int() != int(MethodTypeNo) { procedureIdList = append(procedureIdList, r[dao.TaskProcedureDecisionCondition.Columns.ExamineDraftStage].Int()) } // 根据是否审核作品添加相应的步骤 if r.Map()[dao.TaskProcedureDecisionCondition.Columns.ReviewArticle] != 0 { procedureIdList = append(procedureIdList, r[dao.TaskProcedureDecisionCondition.Columns.ReviewArticleStage].Int()) } // 添加质检链接步骤id procedureIdList = append(procedureIdList, r[dao.TaskProcedureDecisionCondition.Columns.QuilityTestStage].Int()) // 添加数据质检步骤id procedureIdList = append(procedureIdList, r[dao.TaskProcedureDecisionCondition.Columns.DataTestStage].Int()) // 根据是否寄回样品添加相应步骤 //if r[dao.TaskProcedureDecisionCondition.Columns.ReturnSamples].Int() != 0 { // procedureIdList = append(procedureIdList, r[dao.TaskProcedureDecisionCondition.Columns.ReturnSamplesStage].Int()) //} procedureIdList = append(procedureIdList, r[dao.TaskProcedureDecisionCondition.Columns.TaskFisnishStage].Int()) return t.fillOrderWorkflowToDatabase(taskId, orderId, procedureIdList, ctx, tx) } // GetOrderWorkFlow 获取订单的所有流程节点 func (*orderProcedureManager)GetOrderWorkFlow(orderId int) (gdb.Result, error) { res, err := g.DB().Model(dao.WorkflowNodeContainer.Table). OrderAsc(dao.WorkflowNodeContainer.Columns.SortId). All(dao.WorkflowNodeContainer.Columns.OrderId, orderId) return res, err } // handleProcedureOvertimeReduceFee 处理扣费 返回扣费金额和错误 func (*orderProcedureManager)handleProcedureOvertimeReduceFee(procedureNode *model.WorkflowNodeContainer, orderInfo *model.OrderInfo, ctx context.Context, tx *gdb.TX) (int64, error) { // 天数显示为0表示不限制天数 if procedureNode.LimitDays1 <= 0 && procedureNode.LimitDays2 <= 0 { return 0, nil } // 订单处于上传初稿 上传作品 上传链接 上传数据阶段时会产生超期扣费 if procedureNode.ProcedureStage == int(procedureStageDraft) || procedureNode.ProcedureStage == int(procedureStageArticle) || procedureNode.ProcedureStage == int(procedureStageLinkTest) || procedureNode.ProcedureStage == int(procedureStageDataTest) && procedureNode.StepInStage == 1 { days := int(procedureNode.CompleteDate.Sub(procedureNode.StartDate).Seconds() / 86400) overTime := false reduceRatio := 0 if procedureNode.CurExecutionTimes == 1 { if days > procedureNode.LimitDays1 { overTime = true reduceRatio = procedureNode.ReduceFeeRatio1 } } else { if days > procedureNode.LimitDays2 { overTime = true reduceRatio = procedureNode.ReduceFeeRatio2 } } if overTime && reduceRatio > 0{ rec, err := tx.Ctx(ctx).Model(dao.TaskRecruitTalentLevel.Table).One(dao.TaskRecruitTalentLevel.Columns.TrtId, orderInfo.TaskLevelId) if err != nil { return 0, err } // 扣费金额 deductBoBoCoinValue := int64(rec[dao.TaskRecruitTalentLevel.Columns.RewardRoyalties].Float64() * float64(reduceRatio) * 0.01) // 步骤名 var stageName string if procedureNode.CurExecutionTimes <= 2 { stageName = procedureNode.NodeNameFirst } else { stageName = procedureNode.NodeNameAfterSecond } _, err = tx.Ctx(ctx).Model(dao.BobocoinDeductRecord.Table).Insert(model.BobocoinDeductRecord{ TalentId: orderInfo.TalentId, BobocoinValue: deductBoBoCoinValue, OrderId: orderInfo.OrderId, ProcedureStage: procedureNode.ProcedureStage, Reason: DeductReasonOrderOvertime, CreatedAt: gtime.Now(), TaskName: orderInfo.TaskName, OperationStage: stageName, }) if err != nil { return 0, err } return deductBoBoCoinValue, nil } } return 0, nil } // OnOperateOrder 根据当前流程节点的操作成功失败状态决定订单下一步流程节点 func (t *orderProcedureManager)OnOperateOrder(orderId int, isSuccess bool, operatorId int, operatorName string, failedReason ...string) error { err := g.DB().Transaction(context.Background(), func(ctx context.Context, tx *gdb.TX) error { // 获取订单当前状态 var orderInfo *model.OrderInfo err1 := tx.Ctx(ctx).Model(dao.OrderInfo.Table).Where(dao.OrderInfo.Columns.OrderId, orderId).Scan(&orderInfo) if err1 != nil { return err1 } // 任务结束状态为已结束则禁止操作 if orderInfo.CompleteStatus > 1 { return errors.New("order have completed") } var curWorkflowNode *model.WorkflowNodeContainer // 获取订单当前流程节点 err1 = tx.Ctx(ctx).Model(dao.WorkflowNodeContainer.Table). Where("order_id = ? and sort_id = ?", orderInfo.OrderId, orderInfo.OrderStatus).Scan(&curWorkflowNode) if err1 != nil { return err1 } // 根据成功状态分别进入下一个流程节点 var nextStep uint if isSuccess { nextStep = curWorkflowNode.SucNextStep } else { nextStep = curWorkflowNode.FailNextStep } if nextStep == orderFinishCode { // 获取此步骤对应的结束类型 rec, err2 := tx.Ctx(ctx).Model(dao.ROrderCompeleteStageToType.Table). One("order_procedure_stage = ? and order_procedure_step = ?", curWorkflowNode.ProcedureStage, curWorkflowNode.StepInStage) if err2 != nil { return errors.New("query order complete type failed") } // 更新当前流程节点的状态 _, err2 = tx.Ctx(ctx).Model(dao.WorkflowNodeContainer.Table).Update(g.Map{ dao.WorkflowNodeContainer.Columns.CompleteDate: gtime.Now(), dao.WorkflowNodeContainer.Columns.CompleteUserName: operatorName, dao.WorkflowNodeContainer.Columns.State: orderStepStateSuc, }, dao.WorkflowNodeContainer.Columns.ContainerId, curWorkflowNode.ContainerId) if err2 != nil { return err2 } // 记录结束类型到订单表 completeType := rec[dao.ROrderCompeleteStageToType.Columns.CompleteType].Int() // 下一步为任务完结则设置任务结束状态为已结束,并设置结束类型 _, err2 = tx.Ctx(ctx).Model(dao.OrderInfo.Table).Update(g.Map{ dao.OrderInfo.Columns.CompleteStatus: completeType, dao.OrderInfo.Columns.CompleteDate: gtime.Now(), }, dao.OrderInfo.Columns.OrderId, orderId) if err2 != nil { return err1 } // 如果任务正常结束,则将任务稿费写入卜卜币收入表 if completeType == int(OrderCompleteTypeNormal) { _, err2 = tx.Ctx(ctx).Model(dao.BobocoinIncomeRecord.Table).Insert(model.BobocoinIncomeRecord{ BobocoinValue: orderInfo.SettleAmount, OrderId: orderId, RecruitLevelId: orderInfo.TaskLevelId, TalentId: orderInfo.TalentId, CompleteDate: gtime.Now(), TaskName: orderInfo.TaskName, }) if err2 != nil { return err1 } } return nil } // 获取订单下一个流程节点 var nextWorkflowNode *model.WorkflowNodeContainer err1 = tx.Ctx(ctx).Model(dao.WorkflowNodeContainer.Table).Where("order_id = ? and sort_id = ?", orderId, nextStep).Scan(&nextWorkflowNode) if err1 != nil { return err1 } var failResStr string if isSuccess { failResStr = "success" } else { if failedReason == nil { failResStr = "failed" } failResStr = failedReason[0] } if isSuccess { // 如果是执行成功,则更新当前节点为完成状态 _, err2 := tx.Ctx(ctx).Model(dao.WorkflowNodeContainer.Table).Update(g.Map{ dao.WorkflowNodeContainer.Columns.CompleteDate: gtime.Now(), dao.WorkflowNodeContainer.Columns.CompleteUserName: operatorName, dao.WorkflowNodeContainer.Columns.State: orderStepStateSuc, dao.WorkflowNodeContainer.Columns.FailReason: failResStr, }, dao.WorkflowNodeContainer.Columns.ContainerId, curWorkflowNode.ContainerId) if err2 != nil { return err2 } } else { // 执行失败,当前节点状态更新为失败 _, err2 := tx.Ctx(ctx).Model(dao.WorkflowNodeContainer.Table).Update(g.Map{ dao.WorkflowNodeContainer.Columns.CompleteDate: nil, dao.WorkflowNodeContainer.Columns.CompleteUserName: nil, dao.WorkflowNodeContainer.Columns.State: orderStepStateFail, dao.WorkflowNodeContainer.Columns.FailReason: failResStr, }, dao.WorkflowNodeContainer.Columns.ContainerId, curWorkflowNode.ContainerId) if err2 != nil { return err2 } } // 执行扣费 deductValue, err1 := t.handleProcedureOvertimeReduceFee(curWorkflowNode, orderInfo, ctx, tx) if err1 != nil { return err1 } orderSettleAmount := orderInfo.SettleAmount if deductValue > 0 { orderSettleAmount -= deductValue } if isSuccess { // 如果是成功状态则更新下个节点状态,增加下个节点执行次数 _, err1 = tx.Ctx(ctx).Model(dao.WorkflowNodeContainer.Table).Update(g.Map{ dao.WorkflowNodeContainer.Columns.StartDate: gtime.Now(), dao.WorkflowNodeContainer.Columns.CompleteDate: nil, dao.WorkflowNodeContainer.Columns.CompleteUserName: nil, dao.WorkflowNodeContainer.Columns.State: orderStepStateGoingOn, dao.WorkflowNodeContainer.Columns.CurExecutionTimes: nextWorkflowNode.CurExecutionTimes + 1, }, dao.WorkflowNodeContainer.Columns.ContainerId, nextWorkflowNode.ContainerId) if err1 != nil { return err1 } } else { // 如果是失败状态则更新下个节点状态,增加下个节点执行次数并将失败原因添加到下个节点 _, err1 = tx.Ctx(ctx).Model(dao.WorkflowNodeContainer.Table).Update(g.Map{ dao.WorkflowNodeContainer.Columns.StartDate: gtime.Now(), dao.WorkflowNodeContainer.Columns.CompleteDate: nil, dao.WorkflowNodeContainer.Columns.CompleteUserName: nil, dao.WorkflowNodeContainer.Columns.State: orderStepStateGoingOn, dao.WorkflowNodeContainer.Columns.FailReason: failResStr, dao.WorkflowNodeContainer.Columns.CurExecutionTimes: nextWorkflowNode.CurExecutionTimes + 1, }, dao.WorkflowNodeContainer.Columns.ContainerId, nextWorkflowNode.ContainerId) if err1 != nil { return err1 } } // 将下一个流程节点sortId记录在订单状态中 _, err1 = tx.Ctx(ctx).Model(dao.OrderInfo.Table).Update(g.Map{ dao.OrderInfo.Columns.SettleAmount: orderSettleAmount, dao.OrderInfo.Columns.OrderStatus: nextStep, }, dao.OrderInfo.Columns.OrderId, orderId) if err1 != nil { return err1 } // 将操作记录插入记录表 _, err1 = tx.Ctx(ctx).Model(dao.OrderStatusRecord.Table).Insert(model.OrderStatusRecord{ OrderId: orderId, AlterBefore: curWorkflowNode.SortId, AlterAfter: nextWorkflowNode.SortId, RecordId: operatorId, RecordName: operatorName, CreatedAt: gtime.Now(), }) return err1 }) return err }