// Package gen // @Link https://github.com/xiujiecn/xiu-admin // @Copyright Copyright (c) 2026 LiXiujie // @Author Lxj // @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(¤t); 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 = ¤tStatus // 查询当前部门信息 var currentDept entity.SysDept err = dao.SysDept.Ctx(ctx). Where(dao.SysDept.Columns().DeptId, createdDept). Scan(¤tDept) 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, }) }) } 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, ¤t, 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(¤t); 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, ¤t, 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 }