package cms import ( "encoding/json" "fmt" "math" "strings" "time" "git.rosy.net.cn/jx-callback/business/jxutils/ddmsg" "git.rosy.net.cn/baseapi/platformapi/dingdingapi" "git.rosy.net.cn/baseapi/platformapi/jdeclpapi" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/business/auth2" "git.rosy.net.cn/jx-callback/business/jxstore/financial" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/globals/api" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" ) const ( AcceptMaxCount = 2 CancelMaxCount = 5 waybillKgPrice = 200 mtwmMemberPrice = 1100 ) var ( JobTimerMap map[int64]*time.Timer JobAuditTimerMap map[int64]*time.Timer ) func init() { JobTimerMap = make(map[int64]*time.Timer) JobAuditTimerMap = make(map[int64]*time.Timer) } func PublishJob(ctx *jxcontext.Context, jobExt *model.JobExt) (errCode string, err error) { var ( db = dao.GetDB() job = &model.Job{} finishedAt time.Time DayTimeBegin, DayTimeEnd = jxutils.GetDayTime() ) if data, err := json.Marshal(jobExt); err == nil { json.Unmarshal(data, &job) } finishedAt = utils.Str2Time(jobExt.FinishedAtStr) job.FinishedAt = &finishedAt // 需根据任务类型做一些参数判断,比如门店商品链接,地址 switch job.JobCategoryID { case model.JobCategoryIDwmtg: if job.Address == "" { return errCode, fmt.Errorf("外卖推广任务请输入门店地址!") } case model.JobCategoryIDOther: default: return errCode, fmt.Errorf("暂不支持的任务类型! %v", job.JobCategoryID) } // 需根据返现类型做一些参数判断 switch job.CashbackType { case model.JobCashbackPercentage: if job.Percentage <= 0 || job.Percentage > 100 { return errCode, fmt.Errorf("返现比例请输入1-100之间的比例!") } case model.JobCashbackPrice: default: return errCode, fmt.Errorf("暂不支持的返现类型! %v", job.CashbackType) } if job.UserID == "" { return errCode, fmt.Errorf("参数有误!") } if ctx.GetUserID() != job.UserID { return errCode, fmt.Errorf("用户信息已过期,请重新登录!") } if job.FinishedAt.Sub(time.Now()) <= 0 { return errCode, fmt.Errorf("任务截止日期必须大于今天!") } //验证微信绑定 if err = auth2.CheckWeixinminiAuthBind(ctx.GetUserID()); err != nil { return "", err } //发布任务要扣除任务总额的保证金,不够扣就要进行充值 userBill, err := dao.GetUserBill(db, job.UserID, "") if userBill == nil { return errCode, fmt.Errorf("未查询到该用户的账单!") } job.TotalPrice = job.Count * job.AvgPrice if userBill.AccountBalance < job.TotalPrice { return model.ErrCodeAccountBalanceNotEnough, fmt.Errorf("用户余额不足!") } if job.Count <= 0 { return errCode, fmt.Errorf("任务数量不能为0!") } job.SurplusCount = job.Count if job.UserID == "" { return errCode, fmt.Errorf("任务发起人不能为空!") } jobs, err := dao.GetJobsNoPage(db, []string{job.UserID}, nil, nil, nil, DayTimeBegin, DayTimeEnd, false) if len(jobs) > 0 { members, err := dao.GetUserMember(db, job.UserID, model.MemberTypeNormal) if err != nil { return errCode, err } if len(members) <= 0 { return errCode, fmt.Errorf("非会员一天只能发布一起任务,请确认!") } } if job.Address != "" && (job.Lng == 0 || job.Lat == 0) { lng, lat, err := api.AutonaviAPI.GetCoordinateFromAddressByPage(job.Address) if err != nil { return errCode, err } job.Lng = jxutils.StandardCoordinate2Int(lng) job.Lat = jxutils.StandardCoordinate2Int(lat) } dao.WrapAddIDCULDEntity(job, ctx.GetUserName()) dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() if err = dao.CreateEntity(db, job); err != nil { dao.Rollback(db) return } for _, v := range job.JobSteps { dao.WrapAddIDCULDEntity(v, ctx.GetUserName()) v.JobID = job.ID err = dao.CreateEntity(db, v) } for _, v := range job.JobImgs { dao.WrapAddIDCULEntity(v, ctx.GetUserName()) v.JobID = job.ID err = dao.CreateEntity(db, v) } if err != nil { dao.Rollback(db) return } //发布任务要扣除任务总额的保证金,不够扣就要进行充值 if err == nil && job.Status != model.JobStatusFailed { if err = financial.AddExpendUpdateAccount(db, userBill, model.BillTypeDeposit, job.TotalPrice, job.ID); err != nil { dao.Rollback(db) return } } dao.Commit(db) return errCode, err } func CancelPublishJob(ctx *jxcontext.Context, jobID int) (err error) { var ( db = dao.GetDB() ) job := &model.Job{} job.ID = jobID err = dao.GetEntity(db, job) if job.UserID == "" || job.Status == model.JobStatusFailed || job.Status == model.JobStatusOverdue || job.FinishedAt.Sub(time.Now()) <= 0 || job.SurplusCount <= 0 || job.LimitCountType <= 0 { return fmt.Errorf("未找到该任务或该任务状态不正常,无法取消!") } //取消已发布的任务 userBill, err := dao.GetUserBill(db, job.UserID, "") if userBill == nil { return fmt.Errorf("未查询到该用户的账单!") } dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() //如果是固定返现,退给任务发起人剩余数量*单价 //如果是比例返现,推给任务发起人任务总价-接取这个任务的用户实际返现的价格之和 var price int if job.CashbackType == model.JobCashbackPrice { price = job.SurplusCount * job.AvgPrice } else { if billIncomes, err := dao.GetBillIncome(db, jobID); err == nil { for _, v := range billIncomes { price += v.IncomePrice } } price = job.TotalPrice - price } if err = financial.AddIncomeUpdateAccount(db, userBill, model.BillTypeJobCancelOverdue, price, jobID); err != nil { dao.Rollback(db) return } //3、任务状态被取消 job.Status = model.JobStatusFailed job.DeletedAt = time.Now() if _, err = dao.UpdateEntity(db, job, "Status", "DeletedAt"); err != nil { dao.Rollback(db) return } dao.Commit(db) return err } func GetJobs(ctx *jxcontext.Context, userIDs []string, categoryIDs, statuss, vendorIDs []int, includeStep bool, fromTime, toTime string, lng, lat float64, keyword string, sortType, pageSize, offset int) (pagedInfo *model.PagedInfo, err error) { var ( db = dao.GetDB() userID = ctx.GetUserID() ) pagedInfo, err = dao.GetJobs(db, userIDs, categoryIDs, statuss, vendorIDs, []int{model.JobTypeNormal}, includeStep, utils.Str2Time(fromTime), utils.Str2Time(toTime), lng, lat, keyword, sortType, pageSize, offset) //插入用户搜索关键词 if keyword != "" { if userSearchs, _ := dao.GetUserSearch(db, userID, keyword); len(userSearchs) > 0 { userSearchs[0].Count++ userSearchs[0].UpdatedAt = time.Now() dao.UpdateEntity(db, userSearchs[0], "Count", "UpdatedAt") } else { userSearch := &model.UserSearch{ UserID: userID, Keyword: keyword, } dao.WrapAddIDCULEntity(userSearch, ctx.GetUserName()) dao.CreateEntity(db, userSearch) } } return pagedInfo, err } func GetJobDetail(ctx *jxcontext.Context, jobID int, lng, lat float64) (job *dao.GetJobsResult, err error) { var ( db = dao.GetDB() ) job, err = dao.GetJobDetail(db, jobID) if job.Lng != 0 && job.Lat != 0 { job.Distance = jxutils.EarthDistance(lng, lat, jxutils.IntCoordinate2Standard(job.Lng), jxutils.IntCoordinate2Standard(job.Lat)) } return job, err } func AcceptJob(ctx *jxcontext.Context, jobID int) (jobOrderID int64, errCode string, err error) { var ( db = dao.GetDB() userID = ctx.GetUserID() num int DayTimeBegin, DayTimeEnd = jxutils.GetDayTime() WeekTimeBegin, WeekTimeEnd = jxutils.GetWeekTime() ) job := &model.Job{} job.ID = jobID err = dao.GetEntity(db, job) if job.UserID == "" || job.Status == model.JobStatusFailed || job.Status == model.JobStatusOverdue || job.FinishedAt.Sub(time.Now()) <= 0 || job.SurplusCount <= 0 || job.LimitCountType <= 0 { return 0, errCode, fmt.Errorf("未找到该任务或该任务状态不正常,无法接单!") } num, err = checkJobOrders(db, 0, "<= "+utils.Int2Str(model.JobOrderStatusAccept), userID, utils.ZeroTimeValue, utils.ZeroTimeValue) if num >= AcceptMaxCount { return 0, errCode, fmt.Errorf("每人最多接取" + utils.Int2Str(AcceptMaxCount) + "个任务,请核实!") } num, err = checkJobOrders(db, jobID, "<= "+utils.Int2Str(model.JobOrderStatusWaitAudit), userID, utils.ZeroTimeValue, utils.ZeroTimeValue) if num > 0 { return 0, errCode, fmt.Errorf("您还有此任务未完成,请完成后再接取!") } // num, err = checkJobOrders(db, "= "+utils.Int2Str(model.JobOrderStatusAuditUnPass), userID, utils.ZeroTimeValue, utils.ZeroTimeValue) // if num > 0 { // return fmt.Errorf("您还有此任务未审核通过记录,可直接在未审核中重新提交!") // } switch job.LimitCountType { case model.JobLimitCountTypePO: num, err = checkJobOrders(db, jobID, "<> "+utils.Int2Str(model.JobOrderStatusCancel), userID, utils.ZeroTimeValue, utils.ZeroTimeValue) if num > 0 { return 0, errCode, fmt.Errorf("此任务只支持每人做一次!") } case model.JobLimitCountTypePDO: num, err = checkJobOrders(db, jobID, "<> "+utils.Int2Str(model.JobOrderStatusCancel), userID, DayTimeBegin, DayTimeEnd) if num > 0 { return 0, errCode, fmt.Errorf("此任务只支持每人每天做一次!") } case model.JobLimitCountTypePWO: num, err = checkJobOrders(db, jobID, "<> "+utils.Int2Str(model.JobOrderStatusCancel), userID, WeekTimeBegin, WeekTimeEnd) if num > 0 { return 0, errCode, fmt.Errorf("此任务只支持每人每周做一次!") } case model.JobLimitCountTypeNoLimit: default: return 0, errCode, fmt.Errorf("不支持的任务限次类型!%v", job.LimitCountType) } jobOrder := &model.JobOrder{ JobID: jobID, JobOrderID: jxutils.GenJobOrderNo(), UserID: ctx.GetUserID(), // Status: model.JobOrderStatusAccept, } // //美团会员任务 // if job.Type == model.JobTypeMtMember { // jobOrder.Status = model.JobOrderStatusSpec // } else { jobOrder.Status = model.JobOrderStatusAccept // } dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() dao.WrapAddIDCULEntity(jobOrder, ctx.GetUserName()) if err = dao.CreateEntity(db, jobOrder); err != nil { dao.Rollback(db) return } //用户接受任务,任务剩余次数-1 job.SurplusCount -= 1 if _, err = dao.UpdateEntity(db, job, "SurplusCount"); err != nil { dao.Rollback(db) return } //任务限时完成 timer := checkLimitJobOrders(db, job, jobOrder, model.JobTimerTypeAccept) JobTimerMap[jobOrder.JobOrderID] = timer dao.Commit(db) return jobOrder.JobOrderID, errCode, err } func CancelAcceptJob(ctx *jxcontext.Context, jobID int, jobOrderID int64) (err error) { var ( db = dao.GetDB() ) jobOrder := &model.JobOrder{} jobOrder.JobOrderID = jobOrderID err = dao.GetEntity(db, jobOrder, "JobOrderID") if jobOrder.ID != 0 && jobOrder.Status == model.JobOrderStatusCancel { return fmt.Errorf("此任务已被取消了!") } job := &model.Job{} job.ID = jobID err = dao.GetEntity(db, job) dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() //如果当前任务状态正常,剩余数量就加1 if job.Status >= 0 { job.SurplusCount += 1 if _, err = dao.UpdateEntity(db, job, "SurplusCount"); err != nil { dao.Rollback(db) return } } else { userBill, err := dao.GetUserBill(db, job.UserID, "") if userBill == nil { return fmt.Errorf("未查询到该用户的账单!") } //如果状态不正常(取消或者过期)就要把这一笔退回去 //2、账户收入 //是固定返现才会退一笔任务单价 if job.CashbackType == model.JobCashbackPrice { if err = financial.AddIncomeUpdateAccount(db, userBill, model.BillTypeJobCancelOverdue, job.AvgPrice, jobID); err != nil { dao.Rollback(db) return err } } } //3、任务订单状态被取消 jobOrder.Status = model.JobOrderStatusCancel if _, err = dao.UpdateEntity(db, jobOrder, "Status"); err != nil { dao.Rollback(db) return err } dao.Commit(db) return err } func checkJobOrders(db *dao.DaoDB, jobID int, statusCompareStr, userID string, fromTime, toTime time.Time) (num int, err error) { jobOrders, err := dao.GetJobOrdersNoPage(db, jobID, 0, userID, statusCompareStr, fromTime, toTime, nil) return len(jobOrders), err } func checkLimitJobOrders(db *dao.DaoDB, job *model.Job, jobOrder *model.JobOrder, jobTimerType int) (timer *time.Timer) { //插到定时任务表里,主要是重启项目后的重启定时器用 jobTimer := &model.JobTimer{ JobID: job.ID, JobOrderID: jobOrder.JobOrderID, Type: jobTimerType, Status: model.JobTimerStatusWait, StartAt: jobOrder.CreatedAt, LimitAt: job.JobLimitAt, } dao.WrapAddIDCULEntity(jobTimer, jxcontext.AdminCtx.GetUserName()) dao.CreateEntity(db, jobTimer) switch jobTimerType { case model.JobTimerTypeAccept: timer = time.NewTimer(time.Hour * time.Duration(job.JobLimitAt)) case model.JobTimerTypeSubmit: timer = time.NewTimer(time.Hour * time.Duration(job.AuditLimitAt)) } utils.CallFuncAsync(func() { select { case <-timer.C: switch jobTimerType { case model.JobTimerTypeAccept: UpdateLimitJobOrders(db, timer, job.ID, jobOrder, jobTimer) case model.JobTimerTypeSubmit: UpdateLimitAuditJobOrders(db, timer, job.ID, jobOrder, jobTimer) } } }) return timer } func UpdateLimitJobOrders(db *dao.DaoDB, timer *time.Timer, jobID int, jobOrder *model.JobOrder, jobTimer *model.JobTimer) { globals.SugarLogger.Debugf("updateLimitJobOrders jobID: %v, jobOrderID: %v", jobID, jobOrder.JobOrderID) defer timer.Stop() if jobOrder.Status > model.JobOrderStatusAccept { return } jobOrder.Status = model.JobOrderStatusCancel if _, err := dao.UpdateEntity(db, jobOrder, "Status"); err == nil { jobTimer.Status = model.JobTimerStatusFinish dao.UpdateEntity(db, jobTimer, "Status") } } func UpdateLimitAuditJobOrders(db *dao.DaoDB, timer *time.Timer, jobID int, jobOrder *model.JobOrder, jobTimer *model.JobTimer) { globals.SugarLogger.Debugf("checkLimitAuditJobOrders jobID: %v, jobOrderID: %v", jobID, jobOrder.JobOrderID) defer timer.Stop() if jobOrder.Status == model.JobOrderStatusWaitAudit { err := AuditJob(jxcontext.AdminCtx, int(jobOrder.JobOrderID), model.JobOrderStatusAuditPass, "超时系统通过") if err != nil { globals.SugarLogger.Debugf("checkLimitAuditJobOrders err: %v jobID: %v, jobOrderID: %v", err, jobID, jobOrder.JobOrderID) } else { jobTimer.Status = model.JobTimerStatusFinish dao.UpdateEntity(db, jobTimer, "Status") } } } func SubmitJob(ctx *jxcontext.Context, jobOrder *model.JobOrder) (err error) { var ( db = dao.GetDB() ) if jobOrder.JobID == 0 || jobOrder.JobOrderID == 0 { return fmt.Errorf("传入数据有误!") } job := &model.Job{} job.ID = jobOrder.JobID err = dao.GetEntity(db, job) jobOrder2 := &model.JobOrder{} jobOrder2.JobOrderID = jobOrder.JobOrderID err = dao.GetEntity(db, jobOrder2, "JobOrderID") //如果是比例返现 if job.CashbackType == model.JobCashbackPercentage { if jobOrder.UserActualPrice == 0 { return fmt.Errorf("比例返现任务请输入订单实际实付金额,用于任务发起人审核!") } } if jobOrder2.JobID == 0 { return fmt.Errorf("未查询到相应的任务!") } if jobOrder2.Status >= model.JobOrderStatusWaitAudit { return fmt.Errorf("任务订单状态有误!") } if ctx.GetUserID() != jobOrder2.UserID { return fmt.Errorf("任务订单接收人有误!") } jobOrder2.Imgs = jobOrder.Imgs jobOrder2.Content = jobOrder.Content jobOrder2.SubmitAuditAt = time.Now() jobOrder2.UserActualPrice = jobOrder.UserActualPrice jobOrder2.Status = model.JobOrderStatusWaitAudit if _, err = dao.UpdateEntity(db, jobOrder2, "Imgs", "Content", "SubmitAuditAt", "Status", "UserActualPrice"); err == nil { //任务定时器停止 JobTimerMap[jobOrder2.JobOrderID].Stop() //任务定时表状态完成 jobTimer := &model.JobTimer{ JobID: job.ID, JobOrderID: jobOrder2.JobOrderID, Type: model.JobTimerTypeAccept, } if err = dao.GetEntity(db, jobTimer, "JobID", "JobOrderID", "Type"); err == nil { jobTimer.Status = model.JobTimerStatusFinish dao.UpdateEntity(db, jobTimer, "Status") } //审核定时开启 timer := checkLimitJobOrders(db, job, jobOrder2, model.JobTimerTypeSubmit) JobAuditTimerMap[jobOrder2.JobOrderID] = timer } return err } func AuditJob(ctx *jxcontext.Context, jobOrderID, status int, comment string) (err error) { var ( db = dao.GetDB() ) jobOrder := &model.JobOrder{} jobOrder.JobOrderID = int64(jobOrderID) err = dao.GetEntity(db, jobOrder, "JobOrderID") job := &model.Job{} job.ID = jobOrder.JobID err = dao.GetEntity(db, job) if ctx.GetUserID() != job.UserID { return fmt.Errorf("任务发起人才能审核!") } //固定返现 //1、审核时,若此任务已过期或者已取消,不通过则应将此任务保证金退还给发起人,通过则应将单次任务保证金给接受人 //2、若此任务未过期,不通过则此任务剩余数量将+1,通过则应将单次任务保证金给接受人 jobOrder.Status = status jobOrder.Comment = comment jobOrder.AuditAt = time.Now() jobOrder.LastOperator = ctx.GetUserName() userBillJobOrder, err := dao.GetUserBill(db, jobOrder.UserID, "") dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() if _, err = dao.UpdateEntity(db, jobOrder, "Status", "Comment", "AuditAt", "LastOperator"); err != nil { dao.Rollback(db) return } if status == model.JobOrderStatusAuditPass { var price int if job.CashbackType == model.JobCashbackPrice { price = job.AvgPrice } else { price = jobOrder.UserActualPrice * job.Percentage / 100 if price > job.AvgPrice { price = job.AvgPrice } } //若完成任务的人在某个群组中,则要向群主分成 if messageGroupMembers, err := dao.GetMessageGroupMembers(db, 0, model.GroupTypeMulit, jobOrder.UserID); err == nil { if len(messageGroupMembers) > 1 { return fmt.Errorf("审核异常,该任务提交人加入了多个群组!") } else if len(messageGroupMembers) == 1 { if messageGroupsResult, err := dao.GetMessageGroups(db, "", messageGroupMembers[0].GroupID, model.GroupTypeMulit, false, ""); err == nil { if len(messageGroupsResult) == 1 { //不分成 if messageGroupsResult[0].DividePercentage != 0 { if userBillGroupMaster, err := dao.GetUserBill(db, messageGroupsResult[0].UserID, ""); err == nil { if err = financial.AddIncomeUpdateAccount(db, userBillGroupMaster, model.BillTypeDivide, price*messageGroupsResult[0].DividePercentage/100, job.ID); err != nil { dao.Rollback(db) return err } } //接收人账户收入 if err = financial.AddIncomeUpdateAccount(db, userBillJobOrder, model.BillTypeJobDivide, price*(100-messageGroupsResult[0].DividePercentage)/100, job.ID); err != nil { dao.Rollback(db) return err } } else { //接收人账户收入 if err = financial.AddIncomeUpdateAccount(db, userBillJobOrder, model.BillTypeJob, price, job.ID); err != nil { dao.Rollback(db) return err } } } } } else if len(messageGroupMembers) == 0 { //若没有在某个群组,则得到全部 //接收人账户收入 if err = financial.AddIncomeUpdateAccount(db, userBillJobOrder, model.BillTypeJob, price, job.ID); err != nil { dao.Rollback(db) return err } } } jobOrder.Status = model.JobOrderStatusFinish if _, err = dao.UpdateEntity(db, jobOrder, "Status"); err != nil { dao.Rollback(db) return err } } else { if job.Status < 0 { if job.CashbackType == model.JobCashbackPrice { userBill, err := dao.GetUserBill(db, job.UserID, "") if err = financial.AddIncomeUpdateAccount(db, userBill, model.BillTypeJobAuditUnPassWithCancelOverdue, job.AvgPrice, job.ID); err != nil { dao.Rollback(db) return err } } } else { job.SurplusCount += 1 if _, err = dao.UpdateEntity(db, job, "SurplusCount"); err != nil { dao.Rollback(db) return } } } dao.Commit(db) //任务定时器停止 if JobAuditTimerMap[int64(jobOrderID)] != nil { JobAuditTimerMap[int64(jobOrderID)].Stop() } //任务定时表状态完成 jobTimer := &model.JobTimer{ JobID: job.ID, JobOrderID: jobOrder.JobOrderID, Type: model.JobTimerTypeSubmit, } if err = dao.GetEntity(db, jobTimer, "JobID", "JobOrderID", "Type"); err == nil { jobTimer.Status = model.JobTimerStatusFinish dao.UpdateEntity(db, jobTimer, "Status") } return err } func RefreshJobStatus(ctx *jxcontext.Context) (err error) { var ( db = dao.GetDB() ) globals.SugarLogger.Debugf("RefreshJobStatus begin...") jobs, err := dao.GetJobsNoPage(db, nil, nil, []int{model.JobStatusDoing}, nil, utils.ZeroTimeValue, utils.ZeroTimeValue, false) if err != nil { globals.SugarLogger.Debugf("RefreshJobStatus err :%v", err) return } for _, job := range jobs { if time.Now().Sub(*job.FinishedAt) >= 0 { //取消已发布的任务 userBill, err := dao.GetUserBill(db, job.UserID, "") if userBill == nil { return fmt.Errorf("未查询到该用户的账单!") } dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() //如果是固定返现,退给任务发起人剩余数量*单价 //如果是比例返现,推给任务发起人任务总价-接取这个任务的用户实际返现的价格之和 var price int if job.CashbackType == model.JobCashbackPrice { price = job.SurplusCount * job.AvgPrice } else { if billIncomes, err := dao.GetBillIncome(db, job.ID); err == nil { for _, v := range billIncomes { price += v.IncomePrice } } price = job.TotalPrice - price } if err = financial.AddIncomeUpdateAccount(db, userBill, model.BillTypeJobCancelOverdue, price, job.ID); err != nil { dao.Rollback(db) return err } //3、任务状态被取消 job2 := &model.Job{} job2.ID = job.ID dao.GetEntity(db, job2) if job2 != nil { job2.Status = model.JobStatusOverdue // job.DeletedAt = time.Now() if _, err = dao.UpdateEntity(db, job2, "Status"); err != nil { dao.Rollback(db) return err } } dao.Commit(db) } } globals.SugarLogger.Debugf("RefreshJobStatus end...") return err } func ImprotMtMembers(ctx *jxcontext.Context, mtMembers []*model.MtMember) (err error) { var ( db = dao.GetDB() ) for _, v := range mtMembers { v.ShortLink = v.URL[strings.LastIndex(v.URL, "/")+1 : len(v.URL)] dao.WrapAddIDCULDEntity(v, ctx.GetUserName()) } if err = dao.CreateMultiEntities(db, mtMembers); err == nil { job, err := dao.GetJob(db, nil, nil, nil, []int{model.JobTypeMtMember}, utils.ZeroTimeValue, utils.ZeroTimeValue, false) if job != nil && err == nil { job.Count += len(mtMembers) job.SurplusCount += len(mtMembers) dao.UpdateEntity(db, job, "Count", "SurplusCount") } } return err } func RechargeMtMembers(ctx *jxcontext.Context, phone int) (errCode string, err error) { var ( db = dao.GetDB() db2 = dao.GetDB() userID = ctx.GetUserID() ) dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() nums, err := dao.GetMtMembers(db) if nums < 10 { ddmsg.SendUserMessage(dingdingapi.MsgTyeText, "F4836F9238F611EB9101525400C36BDA", "美团会员券", "美团会员券仅剩"+utils.Int2Str(nums)+"张了!") ddmsg.SendUserMessage(dingdingapi.MsgTyeText, "EFA9876238FC11EB9101525400C36BDA", "美团会员券", "美团会员券仅剩"+utils.Int2Str(nums)+"张了!") } if err != nil { dao.Rollback(db) return errCode, err } //验证微信绑定 if err = auth2.CheckWeixinminiAuthBind(userID); err != nil { return "", err } //特殊任务,如美团会员,是直接要支付 userBill, err := dao.GetUserBill(db, userID, "") if err != nil { return errCode, err } if userBill.AccountBalance < mtwmMemberPrice { return model.ErrCodeAccountBalanceNotEnough, fmt.Errorf("用户余额不足,请充值!") } //账户支出 if err = financial.AddExpendUpdateAccount(db, userBill, model.BillTypeSpJob, mtwmMemberPrice, 1); err != nil { dao.Rollback(db) return errCode, err } dao.Commit(db) for { dao.Begin(db2) defer func() { if r := recover(); r != nil { dao.Rollback(db2) panic(r) } }() mtMember, err := dao.GetMtMember(db2) if mtMember == nil { return errCode, fmt.Errorf("补券中,请稍后再试!") } err = api.MtMemberAPI.RechargeExchange(phone, mtMember.ShortLink) mtMember.DeletedAt = time.Now() dao.UpdateEntity(db2, mtMember, "DeletedAt") dao.Commit(db2) if err == nil { job, err := dao.GetJob(db2, nil, nil, nil, []int{model.JobTypeMtMember}, utils.ZeroTimeValue, utils.ZeroTimeValue, false) _, errCode, err = AcceptJob(ctx, job.ID) if errCode != "" { return errCode, err } if err != nil { return errCode, err } break } } return errCode, err } func SendJdDelivery(ctx *jxcontext.Context, dOrder *model.DeliveryOrder) (errCode string, err error) { var ( db = dao.GetDB() ) if dOrder.Weight == 0 { return errCode, fmt.Errorf("重量必须填写!") } if dOrder.PayPrice == 0 { if dOrder.Weight <= 3 { dOrder.PayPrice = 1000 } else { dOrder.PayPrice = 1000 + int(float64(waybillKgPrice)*math.Ceil(dOrder.Weight-3)) } } var ( sendDelivery *dao.UserDeliveryAddressEx receiveDelivery *dao.UserDeliveryAddressEx ) sendDeliveryList, _, err := dao.QueryUserDeliveryAddress(db, int64(dOrder.DeliverySendID), nil, 0, 0, 0) receiveDeliveryList, _, err := dao.QueryUserDeliveryAddress(db, int64(dOrder.DeliveryReceiveID), nil, 0, 0, 0) userBill, err := dao.GetUserBill(db, ctx.GetUserID(), "") if err != nil { return errCode, err } //验证微信绑定 if err = auth2.CheckWeixinminiAuthBind(ctx.GetUserID()); err != nil { return "", err } if userBill.AccountBalance < dOrder.PayPrice { return model.ErrCodeAccountBalanceNotEnough, fmt.Errorf("用户余额不足,请充值!") } if len(sendDeliveryList) == 0 { return errCode, fmt.Errorf("未找到寄件人地址!") } else { sendDelivery = sendDeliveryList[0] } if len(receiveDeliveryList) == 0 { return errCode, fmt.Errorf("未找到取件人地址!") } else { receiveDelivery = receiveDeliveryList[0] } //内蒙古海南统一35 //新疆西藏统一45 if receiveDelivery.ParentCode == 150000 || receiveDelivery.ParentCode == 460000 || receiveDelivery.ParentCode == 650000 || receiveDelivery.ParentCode == 540000 || sendDelivery.ParentCode == 150000 || sendDelivery.ParentCode == 460000 || sendDelivery.ParentCode == 650000 || sendDelivery.ParentCode == 540000 { return errCode, fmt.Errorf("暂不支持该地区的快递业务!") } sendProvinceName := "" receiveProvinceName := "" if place1, err := dao.GetPlaceByCode(db, sendDelivery.CityCode); err == nil { if place2, err2 := dao.GetPlaceByCode(db, place1.ParentCode); err2 == nil { sendProvinceName = place2.Name } } if place1, err := dao.GetPlaceByCode(db, receiveDelivery.CityCode); err == nil { if place2, err2 := dao.GetPlaceByCode(db, place1.ParentCode); err2 == nil { receiveProvinceName = place2.Name } } dao.WrapAddIDCULEntity(dOrder, ctx.GetUserName()) if vendorWaybillID, err := api.JdEclpAPI.WaybillReceive(&jdeclpapi.WaybillReceiveParam{ SalePlat: jdeclpapi.SalePlatSourceDelivery, CustomerCode: jdeclpapi.CustomerCode, OrderID: utils.Int64ToStr(jxutils.GenOrderNo()), SenderName: sendDelivery.ConsigneeName, SenderAddress: sendProvinceName + sendDelivery.CityName + sendDelivery.DistrictName + sendDelivery.Address + sendDelivery.DetailAddress, SenderTel: sendDelivery.ConsigneeMobile, ReceiveName: receiveDelivery.ConsigneeName, ReceiveAddress: receiveProvinceName + receiveDelivery.CityName + receiveDelivery.DistrictName + receiveDelivery.Address + receiveDelivery.DetailAddress, ReceiveTel: receiveDelivery.ConsigneeMobile, Weight: dOrder.Weight, Vloumn: dOrder.Vloumn, PackageCount: dOrder.PackageCount, Description: dOrder.Description, Aging: 5, PromiseTimeType: 1, //特惠送 }); err == nil { dOrder.VendorWaybillID = vendorWaybillID } else { return errCode, err } dOrder.Status = model.OrderStatusNew dOrder.UserID = ctx.GetUserID() dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() //账户支出明细 if err = financial.AddExpendUpdateAccount(db, userBill, model.BillTypeSpJob, dOrder.PayPrice, 2); err != nil { dao.Rollback(db) return } dao.Commit(db) job, err := dao.GetJob(db, nil, nil, nil, []int{model.JobTypeJdDelivery}, utils.ZeroTimeValue, utils.ZeroTimeValue, false) jobOrderID, errCode, err := AcceptJob(ctx, job.ID) dOrder.JobOrderID = utils.Int64ToStr(jobOrderID) dOrder.SendName = sendDelivery.ConsigneeName dOrder.SendMobile = sendDelivery.ConsigneeMobile dOrder.SendAddress = sendDelivery.Address dOrder.SendAutoAddress = sendDelivery.AutoAddress dOrder.SendCityCode = sendDelivery.CityCode dOrder.SendDetailAddress = sendDelivery.DetailAddress dOrder.SendLng = sendDelivery.Lng dOrder.SendLat = sendDelivery.Lat dOrder.ReceiveName = receiveDelivery.ConsigneeName dOrder.ReceiveMobile = receiveDelivery.ConsigneeMobile dOrder.ReceiveAddress = receiveDelivery.Address dOrder.ReceiveAutoAddress = receiveDelivery.AutoAddress dOrder.ReceiveCityCode = receiveDelivery.CityCode dOrder.ReceiveDetailAddress = receiveDelivery.DetailAddress dOrder.ReceiveLng = receiveDelivery.Lng dOrder.ReceiveLat = receiveDelivery.Lat if err = dao.CreateEntity(db, dOrder); err != nil { dao.Rollback(db) return } if errCode != "" { return errCode, err } if err != nil { return errCode, err } return errCode, err } func CancelJdDelivery(ctx *jxcontext.Context, vendorWaybillID, reason string) (err error) { var ( db = dao.GetDB() dOrder = &model.DeliveryOrder{ VendorWaybillID: vendorWaybillID, } DayTimeBegin, DayTimeEnd = jxutils.GetDayTime() ) err = dao.GetEntity(db, dOrder, "VendorWaybillID") userBill, err := dao.GetUserBill(db, ctx.GetUserID(), "") dOrders, err := dao.GetDeliveryOrdersNoPage(db, []string{ctx.GetUserID()}, []int{model.OrderStatusCanceled}, DayTimeBegin, DayTimeEnd, nil) if err != nil { return err } if len(dOrders) > 0 { return fmt.Errorf("抱歉,您已经在今天取消过京东物流订单!") } if dOrder.ID == 0 { return fmt.Errorf("未找到该运单!") } if err = api.JdEclpAPI.CancelWayBill(&jdeclpapi.CancelWayBillParam{ WaybillCode: vendorWaybillID, CustomerCode: jdeclpapi.CustomerCode, Source: "JOS", CancelReason: reason, OperatorName: ctx.GetUserName(), }); err != nil { return err } dOrder.Status = model.OrderStatusCanceled dOrder.OrderFinishedAt = time.Now() dOrder.Comment = reason dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() if _, err = dao.UpdateEntity(db, dOrder, "Status", "OrderFinishedAt", "Comment"); err != nil { dao.Rollback(db) return } if err = financial.AddIncomeUpdateAccount(db, userBill, model.BillTypeSpJob, dOrder.PayPrice, 2); err != nil { dao.Rollback(db) return } if err = CancelAcceptJob(ctx, 2, utils.Str2Int64(dOrder.JobOrderID)); err != nil { dao.Rollback(db) } dao.Commit(db) return err } func GetJdDelivery(ctx *jxcontext.Context, status int, fromTime, toTime string, pageSize, offset int) (pagedInfo *model.PagedInfo, err error) { var ( db = dao.GetDB() statuss []int ) if status != 0 { statuss = append(statuss, status) } pages, _ := dao.GetDeliveryOrders(db, []string{ctx.GetUserID()}, []int{model.OrderStatusNew, model.OrderStatusDelivering}, utils.ZeroTimeValue, utils.ZeroTimeValue, 9999, 0) for _, v := range pages.Data.([]*dao.GetDeliveryOrdersResult) { if v != nil && v.VendorWaybillID != "" { var ( isDeliverying bool isFinished bool ) if results, err := api.JdEclpAPI.QueryDynamicTraceInfo(v.VendorWaybillID); err == nil { for _, result := range results { if result.State == jdeclpapi.TraceInfoStateM640 { isDeliverying = true } if result.State == jdeclpapi.TraceInfoState150 { isFinished = true } } } if isFinished { v.Status = model.OrderStatusFinished } else if isDeliverying { v.Status = model.OrderStatusDelivering } else { continue } dao.UpdateEntity(db, v, "Status") } } return dao.GetDeliveryOrders(db, []string{ctx.GetUserID()}, statuss, utils.Str2Time(fromTime), utils.Str2Time(toTime), pageSize, offset) } func GetJdDeliveryDetail(ctx *jxcontext.Context, vendorWaybillID string) (queryDynamicTraceInfo []*jdeclpapi.QueryDynamicTraceInfoResult, err error) { return api.JdEclpAPI.QueryDynamicTraceInfo(vendorWaybillID) } func CheckJdDeliveryWeight(ctx *jxcontext.Context) (err error) { var ( db = dao.GetDB() ) userBill, err := dao.GetUserBill(db, ctx.GetUserID(), "") if userBill.AccountBalance < 0 { return fmt.Errorf("您有京东物流订单实际超重,请支付欠款!") } deliveryOrders, err := dao.GetDeliveryOrdersNoPage(db, []string{ctx.GetUserID()}, nil, utils.ZeroTimeValue, utils.ZeroTimeValue, []int{0}) if err != nil { return err } task := tasksch.NewParallelTask("CheckJdDeliveryWeight", tasksch.NewParallelConfig().SetParallelCount(1).SetIsContinueWhenError(true), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { deliveryOrder := batchItemList[0].(*model.DeliveryOrder) waybill, err := api.JdEclpAPI.WaybillQuery(deliveryOrder.VendorWaybillID) if err == nil { return retVal, err } if waybill.DeliveryID == "" { return retVal, err } if waybill.DeliveryID == deliveryOrder.VendorWaybillID { deliveryOrder.IsWeight = 1 if waybill.Weight > 3 && math.Floor(deliveryOrder.Weight) < math.Floor(waybill.Weight) { diffPrice := (math.Floor(waybill.Weight) - math.Floor(deliveryOrder.Weight)) * waybillKgPrice if err != nil { return retVal, err } if err = financial.AddExpendUpdateAccount(db, userBill, model.BillTypeJdWaybillOverWeight, utils.Float64TwoInt(diffPrice), 2); err != nil { return retVal, err } deliveryOrder.IsWeight = 2 } } deliveryOrder.ActualWeight = waybill.Weight dao.UpdateEntity(db, deliveryOrder, "IsWeight", "ActualWeight") return retVal, err }, deliveryOrders) tasksch.HandleTask(task, nil, true).Run() task.GetID() return err } func ResetJobTimers() { var ( db = dao.GetDB() ) jobTimers, err := dao.GetJobTimers(db, model.JobTimerStatusWait) if err != nil { return } task := tasksch.NewParallelTask("ResetJobTimers", tasksch.NewParallelConfig().SetParallelCount(1).SetIsContinueWhenError(true), jxcontext.AdminCtx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { jobTimer := batchItemList[0].(*model.JobTimer) switch jobTimer.Type { case model.JobTimerTypeAccept: timer := time.NewTimer(jobTimer.StartAt.Add(time.Duration(jobTimer.LimitAt) * time.Hour).Sub(time.Now())) jobOrders, err := dao.GetJobOrdersNoPage(db, jobTimer.JobID, jobTimer.JobOrderID, "", "", utils.ZeroTimeValue, utils.ZeroTimeValue, nil) if err != nil { return retVal, err } utils.CallFuncAsync(func() { select { case <-timer.C: switch jobTimer.Type { case model.JobTimerTypeAccept: UpdateLimitJobOrders(db, timer, jobTimer.JobID, jobOrders[0], jobTimer) case model.JobTimerTypeSubmit: UpdateLimitAuditJobOrders(db, timer, jobTimer.JobID, jobOrders[0], jobTimer) } } }) case model.JobTimerTypeSubmit: default: return retVal, err } return retVal, err }, jobTimers) tasksch.HandleTask(task, nil, true).Run() task.GetID() }