Files
jx-callback/business/jxstore/cms/job.go
苏尹岚 4a7f691525 aa
2020-12-21 18:18:21 +08:00

1193 lines
39 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 job.JobCityCode != model.JobCountrywideCode {
_, _, job.JobCityCode, err = getAddressInfoFromCoord(db, job.JobLng, job.JobLat)
}
//验证微信绑定
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, 0, 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, span int, keyword string, sortType, pageSize, offset int) (pagedInfo *model.PagedInfo, err error) {
var (
db = dao.GetDB()
userID = ctx.GetUserID()
_, _, cityCode, _ = getAddressInfoFromCoord(db, lng, lat)
)
pagedInfo, err = dao.GetJobs(db, userIDs, categoryIDs, statuss, vendorIDs, []int{model.JobTypeNormal}, includeStep, utils.Str2Time(fromTime), utils.Str2Time(toTime), lng, lat, cityCode, span, 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))
}
utils.CallFuncAsync(func() {
job2 := &model.Job{}
job2.ID = job.ID
dao.GetEntity(db, job2)
job2.BrowseCount++
dao.UpdateEntity(db, job2, "BrowseCount")
})
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, 0, 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 {
utils.CallFuncAsync(func() {
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 {
dao.Rollback(db)
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
}
}
}
dOrder := &model.DeliveryOrder{}
dOrder.ID = v.ID
if err = dao.GetEntity(db, dOrder); err == nil {
if isFinished {
dOrder.Status = model.OrderStatusFinished
} else if isDeliverying {
dOrder.Status = model.OrderStatusDelivering
} else {
continue
}
dao.UpdateEntity(db, dOrder, "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)
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)
}
}
})
return retVal, err
}, jobTimers)
tasksch.HandleTask(task, nil, true).Run()
task.GetID()
}
func CreateJobSpan(ctx *jxcontext.Context, jobIDs []int, endAt string, span int) (err error) {
var (
db = dao.GetDB()
maxSeq = 0
)
if span == model.JobSpanTop {
maxSeq, err = dao.GetMaxJobTopSeq(db)
} else {
maxSeq, err = dao.GetMaxJobRecmdSeq(db)
}
for k, jobID := range jobIDs {
endAt2 := utils.Str2Time(endAt)
jonSpan := &model.JobSpan{
JobID: jobID,
EndAt: &endAt2,
SpanType: span,
}
if err = dao.CreateEntity(db, jonSpan); err == nil {
job := &model.Job{}
job.ID = jobID
if err = dao.GetEntity(db, job); err == nil {
if span == model.JobSpanTop {
job.TopSeq = k + 1 + maxSeq
job.JobSpanTop = 1
dao.UpdateEntity(db, job, "TopSeq", "JobSpanTop")
} else {
job.RecmdSeq = k + 1 + maxSeq
job.JobSpanRecmd = 1
dao.UpdateEntity(db, job, "RecmdSeq", "JobSpanRecmd")
}
}
}
}
return err
}
func RefreshJobSpan(ctx *jxcontext.Context) (err error) {
var (
db = dao.GetDB()
)
jobSpans, err := dao.GetJobSpans(db)
task := tasksch.NewParallelTask("RefreshJobSpan", tasksch.NewParallelConfig().SetIsContinueWhenError(true), jxcontext.AdminCtx,
func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) {
jobSpan := batchItemList[0].(*model.JobSpan)
if time.Now().Sub(*jobSpan.EndAt) >= 0 {
job := &model.Job{}
job.ID = jobSpan.JobID
if err = dao.GetEntity(db, job); err == nil {
if jobSpan.SpanType == model.JobSpanTop {
job.TopSeq = 0
job.JobSpanTop = 0
dao.UpdateEntity(db, job, "TopSeq", "JobSpanTop")
} else {
job.RecmdSeq = 0
job.JobSpanRecmd = 0
dao.UpdateEntity(db, job, "RecmdSeq", "JobSpanRecmd")
}
}
jobSpan.DeletedAt = time.Now()
dao.UpdateEntity(db, jobSpan, "DeletedAt")
}
return retVal, err
}, jobSpans)
tasksch.HandleTask(task, nil, true).Run()
task.GetID()
return err
}
func ReloadJobSpan(ctx *jxcontext.Context, jobIDs []int, span int) (err error) {
var (
db = dao.GetDB()
)
jobs, err := dao.GetJobsNoPage(db, nil, nil, []int{model.JobStatusDoing}, nil, utils.ZeroTimeValue, utils.ZeroTimeValue, span, false)
if len(jobs) != len(jobIDs) {
return fmt.Errorf("传入的任务IDs有误")
}
for k, v := range jobIDs {
job := &model.Job{}
job.ID = v
if err = dao.GetEntity(db, job); err == nil {
if span == model.JobSpanTop {
if job.JobSpanTop == model.JobSpanTop {
job.TopSeq = k + 1
dao.UpdateEntity(db, job, "TopSeq")
} else {
continue
}
} else {
if job.JobSpanRecmd == 1 {
job.RecmdSeq = k + 1
dao.UpdateEntity(db, job, "RecmdSeq")
} else {
continue
}
}
}
}
return err
}