Files
iotlcm/server/internal/logic/common/t_wo_main.go
T
2026-10-29 14:36:49 +08:00

602 lines
20 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 gen
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2026 LiXiujie
// @Author Lxj <xxx@example.com>
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
// @AutoGenerate Version
package common
import (
"context"
"fmt"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rabbitmq"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/websocket"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/hook"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/excel"
"strings"
"time"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
const (
workOrderAutoCloseDelay = 72 * time.Hour
workOrderStatusAssigned = 1 //已分派
workOrderStatusProcessing = 2 //处理中
workOrderStatusResolved = 3 //已解决
workOrderStatusClosed = 4 //已关单
workOrderStatusCanceled = 5 //已取消
workOrderOperationCreate = 0 //创建
workOrderOperationAssign = 1 //分派
workOrderOperationProcess = 2 //处理
workOrderOperationFinish = 3 //完成
workOrderOperationClose = 4 //关单
workOrderOperationCancel = 5 //取消
)
type sCommonTWoMain struct{}
func NewCommonTWoMain() *sCommonTWoMain {
return &sCommonTWoMain{}
}
func init() {
service.RegisterCommonTWoMain(NewCommonTWoMain())
}
func getWorkOrder(ctx context.Context, woNo string) (current entity.TWoMain, err error) {
if contexts.GetUserId(ctx) <= 0 || strings.TrimSpace(woNo) == "" {
return current, gerror.New("请登录并指定工单编号")
}
query := dao.TWoMain.Ctx(ctx).Where(dao.TWoMain.Columns().TenantId, contexts.GetTenantId(ctx)).Where(dao.TWoMain.Columns().WoNo, strings.TrimSpace(woNo))
if g.DB().GetConfig().Type != "sqlite" {
query = query.LockUpdate()
}
if err = query.Scan(&current); err != nil {
return current, gerror.Wrap(err, "query work order failed")
}
if current.WoNo == "" {
return current, gerror.New("work order not found")
}
if !contexts.IsSuperAdmin(ctx) && current.CreatorUserId != contexts.GetUserId(ctx) && current.HandlerDeptId != contexts.GetDeptId(ctx) && current.HandlerUserId != contexts.GetUserId(ctx) {
return current, gerror.New("无权访问该工单")
}
return current, nil
}
func (s *sCommonTWoMain) notifyWorkOrderChanged(ctx context.Context, woNo string) {
if woNo == "" {
return
}
websocket.SendToTag("ws:tag:wo:communication:"+woNo, &websocket.WResponse{
Event: "ws:event:wo:communication:transfer",
Data: g.Map{
"woNo": woNo,
},
})
}
// Model returns the work order main ORM model
func (s *sCommonTWoMain) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
if len(option) == 0 {
option = append(option, &handler.Option{
FilterTenant: true,
FilterAuth: true,
})
}
return handler.Model(dao.TWoMain.Ctx(ctx), option...)
}
// List returns work order main records.
func (s *sCommonTWoMain) List(ctx context.Context, in *model.TWoMainListParam) (list []*model.TWoMainListModel, totalCount int, err error) {
if contexts.GetUserId(ctx) <= 0 {
return nil, 0, gerror.New("请先登录")
}
mod := s.Model(ctx, &handler.Option{FilterTenant: true, FilterAuth: false})
mod = mod.Fields(model.TWoMainListModel{})
if in.Id > 0 {
mod = mod.Where(dao.TWoMain.Columns().Id, in.Id)
}
if in.CurrentStatus > 0 {
mod = mod.Where(dao.TWoMain.Columns().CurrentStatus, in.CurrentStatus)
}
if len(in.CreatedAt) == 2 {
in.CreatedAt[1] = in.CreatedAt[1].EndOfDay()
mod = mod.WhereBetween(dao.TWoMain.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
}
if in.WoNo != "" {
mod = mod.Where(dao.TWoMain.Columns().WoNo, in.WoNo)
}
if in.WoType > 0 {
mod = mod.Where(dao.TWoMain.Columns().WoType, in.WoType)
}
if !contexts.IsSuperAdmin(ctx) {
columns := dao.TWoMain.Columns()
mod = mod.Where("("+columns.CreatorUserId+" = ? OR "+columns.HandlerUserId+" = ? OR "+columns.HandlerDeptId+" = ?)", contexts.GetUserId(ctx), contexts.GetUserId(ctx), contexts.GetDeptId(ctx))
}
mod = mod.Page(in.Page, in.PageSize)
mod = mod.OrderDesc(dao.TWoMain.Columns().Id)
mod = mod.Hook(hook.MemberSummary)
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
err = gerror.Wrap(err, "get work order list failed")
return
}
return
}
func (s *sCommonTWoMain) Export(ctx context.Context, in *model.TWoMainListParam) (err error) {
list, totalCount, err := s.List(ctx, in)
if err != nil {
return
}
tags, err := convert.GetEntityDescTags(model.TWoMainExportModel{})
if err != nil {
return
}
var (
fileName = "export-work-order-main-" + gctx.CtxId(ctx)
sheetName = fmt.Sprintf("total %v rows, %v pages, page %v, current %v rows", totalCount, (totalCount+in.PageSize-1)/in.PageSize, in.Page, len(list))
exports []model.TWoMainExportModel
)
if err = gconv.Scan(list, &exports); err != nil {
return
}
err = excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
return
}
// 添加工单
func (s *sCommonTWoMain) Edit(ctx context.Context, in *model.TWoMainEditParam) (err error) {
if in.Id > 0 && in.WoNo != nil {
err = xgorm.IsUnique(ctx, &dao.TWoMain, g.Map{dao.TWoMain.Columns().WoNo: in.WoNo}, "work order number exists", in.Id)
}
if err != nil {
return
}
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
if in.Id > 0 {
updatedBy := contexts.GetUserId(ctx)
in.UpdatedBy = &updatedBy
in.UpdatedAt = gtime.Now()
if _, err = s.Model(ctx).
WherePri(in.Id).
Fields(model.TWoMainUpdateFields{}).
WherePri(in.Id).Data(in).Update(); err != nil {
err = gerror.Wrap(err, "update work order failed")
}
return
}
tenantId := contexts.GetTenantId(ctx)
in.TenantId = &tenantId
createdDept := contexts.GetDeptId(ctx)
in.CreatedDept = &createdDept
createdBy := contexts.GetUserId(ctx)
in.CreatedBy = &createdBy
in.CreatedAt = gtime.Now()
woNo := fmt.Sprintf("gd%d", gtime.TimestampMilli())
in.WoNo = &woNo
in.CreatorDeptId = &createdDept
in.CreatorUserId = &createdBy
creatorName := contexts.GetUserName(ctx)
in.CreatorName = &creatorName
currentStatus := workOrderStatusProcessing
in.CurrentStatus = &currentStatus
// 查询当前部门信息
var currentDept entity.SysDept
err = dao.SysDept.Ctx(ctx).
Where(dao.SysDept.Columns().DeptId, createdDept).
Scan(&currentDept)
if err != nil {
return err
}
// 分派到上级部门
var handlerDeptId int64
var handlerUserId int64
if currentDept.ParentId != 0 && currentDept.ParentId != 100 {
// 如果有上级部门,分派到上级部门
handlerDeptId = currentDept.ParentId
var parentDept entity.SysDept
err = dao.SysDept.Ctx(ctx).
Where(dao.SysDept.Columns().DeptId, handlerDeptId).
Scan(&parentDept)
if err != nil {
return err
}
handlerUserId = gconv.Int64(parentDept.Leader)
} else {
// 如果没有上级部门,分派到当前部门
handlerDeptId = createdDept
handlerUserId = gconv.Int64(currentDept.Leader)
}
in.HandlerDeptId = &handlerDeptId
in.HandlerUserId = &handlerUserId
if _, err = tx.Ctx(ctx).Model(dao.TWoMain.Table()).
Fields(model.TWoMainInsertFields{}).
Data(in).Insert(); err != nil {
err = gerror.Wrap(err, "create work order failed")
return err
}
content := ""
if in.Content != nil {
content = *in.Content
}
attachmentUrls := append([]string{}, in.AttachmentUrls...)
if len(attachmentUrls) == 0 && in.Attachment != nil {
attachment := strings.TrimSpace(*in.Attachment)
if attachment != "" {
attachmentUrls = append(attachmentUrls, attachment)
}
}
creatorType := 0
if in.CreatorType != nil {
creatorType = *in.CreatorType
}
if err = service.CommonTWoCommunication().CreateInitialMessage(ctx, tx, &model.TWoCommunicationInitialMessageParam{
WoNo: *in.WoNo,
CommType: 1,
SenderType: creatorType,
SenderDeptId: createdDept,
SenderUserId: createdBy,
SenderName: creatorName,
Content: content,
AttachmentUrls: attachmentUrls,
TenantId: tenantId,
CreatedDept: createdDept,
CreatedBy: createdBy,
CreatedAt: in.CreatedAt,
}); err != nil {
return err
}
return service.CommonTWoRecord().AddOperation(ctx, &model.TWoRecordAddOperationParam{
WoNo: *in.WoNo,
OperationType: workOrderOperationCreate,
AfterStatus: currentStatus,
})
})
}
func (s *sCommonTWoMain) Message(ctx context.Context, in *model.TWoMainMessageParam) (err error) {
return service.CommonTWoCommunication().Message(ctx, in)
}
// 转单:转给顶级部门(ParentId=0)的负责人
func (s *sCommonTWoMain) Transfer(ctx context.Context, in *model.TWoMainTransferParam) (err error) {
if in == nil {
return gerror.New("invalid request")
}
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
current, err := getWorkOrder(ctx, in.WoNo)
if err != nil {
return err
}
if current.CurrentStatus != workOrderStatusAssigned && current.CurrentStatus != workOrderStatusProcessing {
return gerror.New("工单状态不允许此操作")
}
if current.CreatorUserId == contexts.GetUserId(ctx) && !contexts.IsSuperAdmin(ctx) {
return gerror.New("请由处理人员执行此操作")
}
updatedBy := contexts.GetUserId(ctx)
// 1. 查找顶级部门(ParentId = 0)
var topDept entity.SysDept
err = dao.SysDept.Ctx(ctx).
Where(dao.SysDept.Columns().ParentId, 0).
Where(dao.SysDept.Columns().TenantId, current.TenantId).
OrderAsc(dao.SysDept.Columns().OrderNum).OrderAsc(dao.SysDept.Columns().DeptId).
Scan(&topDept)
if err != nil {
return gerror.Wrap(err, "查询顶级部门失败")
}
if topDept.DeptId == 0 {
return gerror.New("未找到顶级部门")
}
// 3. 更新工单,转给顶级部门及其负责人
if _, err = dao.TWoMain.Ctx(ctx).TX(tx).Where(dao.TWoMain.Columns().TenantId, current.TenantId).Where(dao.TWoMain.Columns().CurrentStatus, current.CurrentStatus).
Where(dao.TWoMain.Columns().WoNo, in.WoNo).
Data(do.TWoMain{
AssignDeptId: topDept.DeptId,
AssignUserId: topDept.Leader,
HandlerDeptId: topDept.DeptId,
HandlerUserId: topDept.Leader,
CurrentStatus: workOrderStatusProcessing,
UpdatedBy: updatedBy,
}).Update(); err != nil {
return gerror.Wrap(err, "转单失败")
}
// 4. 记录操作日志
operationType := workOrderOperationAssign
beforeStatus := current.CurrentStatus
afterStatus := workOrderStatusProcessing
return service.CommonTWoRecord().AddOperation(ctx, &model.TWoRecordAddOperationParam{
WoNo: in.WoNo,
OperationType: operationType,
OperationDeptId: topDept.DeptId,
OperationUserId: topDept.Leader,
TargetDeptId: topDept.DeptId,
BeforeStatus: beforeStatus,
AfterStatus: afterStatus,
})
})
if err != nil {
return err
}
s.notifyWorkOrderChanged(ctx, in.WoNo)
return nil
}
// 解决
func (s *sCommonTWoMain) Resolve(ctx context.Context, in *model.TWoMainResolveParam) (err error) {
if in == nil {
return gerror.New("invalid request")
}
var resolvedAt *gtime.Time
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
current, err := getWorkOrder(ctx, in.WoNo)
if err != nil {
return err
}
if current.CurrentStatus != workOrderStatusAssigned && current.CurrentStatus != workOrderStatusProcessing {
return gerror.New("工单状态不允许此操作")
}
if current.CreatorUserId == contexts.GetUserId(ctx) && !contexts.IsSuperAdmin(ctx) {
return gerror.New("请由处理人员执行此操作")
}
updatedBy := contexts.GetUserId(ctx)
creatorDept := contexts.GetDeptId(ctx)
now := gtime.Now()
if _, err = dao.TWoMain.Ctx(ctx).TX(tx).Where(dao.TWoMain.Columns().TenantId, current.TenantId).Where(dao.TWoMain.Columns().CurrentStatus, current.CurrentStatus).
Where(dao.TWoMain.Columns().WoNo, in.WoNo).
Data(do.TWoMain{
CurrentStatus: workOrderStatusResolved,
ResolveTime: now,
HandlerDeptId: creatorDept,
HandlerUserId: updatedBy,
UpdatedBy: updatedBy,
}).Update(); err != nil {
return gerror.Wrap(err, "operation failed")
}
if err = service.CommonTWoRecord().AddOperation(ctx, &model.TWoRecordAddOperationParam{
WoNo: in.WoNo,
OperationType: workOrderOperationFinish,
BeforeStatus: current.CurrentStatus,
AfterStatus: workOrderStatusResolved,
}); err != nil {
return err
}
if err = saveWorkOrderNotice(ctx, tx, &current, updatedBy, "工单待确认解决", "问题已标记解决,请在72小时内确认或反馈未解决。"); err != nil {
return err
}
resolvedAt = now
return nil
})
if err != nil {
return err
}
if resolvedAt != nil {
s.publishAutoClose(ctx, in.WoNo, resolvedAt)
}
return nil
}
func (s *sCommonTWoMain) publishAutoClose(ctx context.Context, woNo string, resolvedAt *gtime.Time) {
if !rabbitmq.Enabled(ctx) {
g.Log().Errorf(ctx, "work order auto close skipped, rabbitmq disabled, woNo:%s", woNo)
return
}
message := model.WorkOrderAutoCloseMessage{
WoNo: woNo,
ResolveTimeTs: resolvedAt.Timestamp(),
}
if err := rabbitmq.PublishDelayed(ctx, consts.QueueWorkOrderAutoCloseDelay, consts.QueueWorkOrderAutoClose, workOrderAutoCloseDelay, message); err != nil {
g.Log().Errorf(ctx, "publish work order auto close failed, woNo:%s, err:%+v", woNo, err)
return
}
g.Log().Infof(ctx, "work order auto close published, woNo:%s, delay:%s", woNo, workOrderAutoCloseDelay)
}
func (s *sCommonTWoMain) AutoCloseResolved(ctx context.Context, message model.WorkOrderAutoCloseMessage) error {
if message.WoNo == "" {
return nil
}
g.Log().Infof(ctx, "work order auto close received, woNo:%s", message.WoNo)
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
current := entity.TWoMain{}
if err := tx.Ctx(ctx).Model(dao.TWoMain.Table()).
Where(dao.TWoMain.Columns().WoNo, message.WoNo).
Scan(&current); err != nil {
return gerror.Wrap(err, "query work order failed")
}
if current.WoNo == "" || current.CurrentStatus != workOrderStatusResolved {
return nil
}
if current.ResolveTime == nil || current.ResolveTime.Add(72*time.Hour).After(gtime.Now()) {
return nil
}
if message.ResolveTimeTs > 0 && (current.ResolveTime == nil || current.ResolveTime.Timestamp() != message.ResolveTimeTs) {
g.Log().Infof(ctx, "work order auto close skipped, resolve time changed, woNo:%s, messageResolveTime:%d", message.WoNo, message.ResolveTimeTs)
return nil
}
operatorID := int64(0)
now := gtime.Now()
result, err := tx.Ctx(ctx).Model(dao.TWoMain.Table()).
Where(dao.TWoMain.Columns().Id, current.Id).
Where(dao.TWoMain.Columns().TenantId, current.TenantId).
Where(dao.TWoMain.Columns().ResolveTime, current.ResolveTime).
Where(dao.TWoMain.Columns().CurrentStatus, workOrderStatusResolved).
Data(do.TWoMain{
CurrentStatus: workOrderStatusClosed,
CloseTime: now,
UpdatedBy: operatorID,
}).Update()
if err != nil {
return gerror.Wrap(err, "auto close work order failed")
}
affected, err := result.RowsAffected()
if err != nil || affected == 0 {
return err
}
_, err = tx.Ctx(ctx).Model(dao.TWoRecord.Table()).Data(do.TWoRecord{
WoNo: message.WoNo,
OperationType: workOrderOperationClose,
OperationDeptId: current.HandlerDeptId,
OperationUserId: operatorID,
BeforeStatus: workOrderStatusResolved,
AfterStatus: workOrderStatusClosed,
TenantId: current.TenantId,
CreatedDept: current.CreatedDept,
CreatedBy: operatorID,
}).Insert()
if err != nil {
return gerror.Wrap(err, "保存自动完结记录失败")
}
return saveWorkOrderNotice(ctx, tx, &current, 0, "工单已自动完结", "问题标记解决超过72小时,工单已自动完结。")
})
}
// 关单
func (s *sCommonTWoMain) Close(ctx context.Context, in *model.TWoMainCloseParam) (err error) {
if in == nil {
return gerror.New("invalid request")
}
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
current, err := getWorkOrder(ctx, in.WoNo)
if err != nil {
return err
}
if current.CurrentStatus != workOrderStatusResolved {
return gerror.New("工单状态不允许此操作")
}
updatedBy := contexts.GetUserId(ctx)
now := gtime.Now()
if _, err = dao.TWoMain.Ctx(ctx).TX(tx).Where(dao.TWoMain.Columns().TenantId, current.TenantId).Where(dao.TWoMain.Columns().CurrentStatus, current.CurrentStatus).
Where(dao.TWoMain.Columns().WoNo, in.WoNo).
Data(do.TWoMain{
CurrentStatus: workOrderStatusClosed,
CloseTime: now,
UpdatedBy: updatedBy,
}).Update(); err != nil {
return gerror.Wrap(err, "operation failed")
}
return service.CommonTWoRecord().AddOperation(ctx, &model.TWoRecordAddOperationParam{
WoNo: in.WoNo,
OperationType: workOrderOperationClose,
BeforeStatus: current.CurrentStatus,
AfterStatus: workOrderStatusClosed,
})
})
}
// 取消
func (s *sCommonTWoMain) Cancel(ctx context.Context, in *model.TWoMainCancelParam) (err error) {
if in == nil {
return gerror.New("invalid request")
}
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
current, err := getWorkOrder(ctx, in.WoNo)
if err != nil {
return err
}
if current.CurrentStatus != workOrderStatusAssigned && current.CurrentStatus != workOrderStatusProcessing {
return gerror.New("工单状态不允许此操作")
}
if current.CreatorUserId != contexts.GetUserId(ctx) && !contexts.IsSuperAdmin(ctx) {
return gerror.New("只有创建人可以取消工单")
}
updatedBy := contexts.GetUserId(ctx)
now := gtime.Now()
if _, err = dao.TWoMain.Ctx(ctx).TX(tx).Where(dao.TWoMain.Columns().TenantId, current.TenantId).Where(dao.TWoMain.Columns().CurrentStatus, current.CurrentStatus).
Where(dao.TWoMain.Columns().WoNo, in.WoNo).
Data(do.TWoMain{
CurrentStatus: workOrderStatusCanceled,
CloseTime: now,
UpdatedBy: updatedBy,
}).Update(); err != nil {
return gerror.Wrap(err, "operation failed")
}
return service.CommonTWoRecord().AddOperation(ctx, &model.TWoRecordAddOperationParam{
WoNo: in.WoNo,
OperationType: workOrderOperationCancel,
BeforeStatus: current.CurrentStatus,
AfterStatus: workOrderStatusCanceled,
})
})
}
// 评价
func (s *sCommonTWoMain) Evaluate(ctx context.Context, in *model.TWoMainEvaluateParam) error {
if in == nil || in.SatisfactionLevel < 1 || in.SatisfactionLevel > 5 {
return gerror.New("请选择1至5星评价")
}
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
current, err := getWorkOrder(ctx, in.WoNo)
if err != nil {
return err
}
userID := contexts.GetUserId(ctx)
if current.CreatorUserId != userID || current.CurrentStatus != workOrderStatusClosed || current.EvalTime != nil || current.SatisfactionLevel != 0 {
return gerror.New("只有创建人可以对已完结工单评价一次")
}
result, err := dao.TWoMain.Ctx(ctx).TX(tx).Where(dao.TWoMain.Columns().Id, current.Id).Where(dao.TWoMain.Columns().TenantId, current.TenantId).WhereNull(dao.TWoMain.Columns().EvalTime).Data(do.TWoMain{SatisfactionLevel: in.SatisfactionLevel, EvalContent: in.EvalContent, EvalTime: gtime.Now(), UpdatedBy: userID}).Update()
if err != nil {
return gerror.Wrap(err, "保存评价失败")
}
count, err := result.RowsAffected()
if err != nil {
return err
}
if count != 1 {
return gerror.New("工单已评价,请刷新")
}
return nil
})
}
func (s *sCommonTWoMain) Delete(ctx context.Context, in *model.TWoMainDeleteParam) (err error) {
if _, err = s.Model(ctx).WherePri(in.Id).Unscoped().Delete(); err != nil {
err = gerror.Wrap(err, "get work order list failed")
return
}
return
}
// View returns a work order main record.
func (s *sCommonTWoMain) View(ctx context.Context, in *model.TWoMainViewParam) (res *model.TWoMainViewModel, err error) {
if err = s.Model(ctx).WherePri(in.Id).Hook(hook.MemberSummary).Scan(&res); err != nil {
err = gerror.Wrap(err, "get work order list failed")
return
}
return
}