package app import ( "context" "strings" "git.5iot.com/iotlcm-public/iotlcm-app/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/dao" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity" "git.5iot.com/iotlcm-public/iotlcm-app/internal/service" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/os/gtime" ) const ( appWorkOrderCreatorTerminal = 1 appWorkOrderCreatorEnterprise = 2 appWorkOrderStatusAssigned = 1 appWorkOrderStatusProcessing = 2 appWorkOrderStatusResolved = 3 appWorkOrderStatusClosed = 4 appWorkOrderStatusCanceled = 5 appWorkOrderOperationCreate = 0 ) type sAppWorkOrder struct{} func NewAppWorkOrder() *sAppWorkOrder { return &sAppWorkOrder{} } func init() { service.RegisterAppWorkOrder(NewAppWorkOrder()) } func (s *sAppWorkOrder) Upload(ctx context.Context) (output *model.SysOssUploadModel, err error) { if err = appEnsureFeatureAccess(ctx, "workorder:create"); err != nil { return nil, err } return service.SysOss().Upload(ctx, &model.SysOssUploadParam{FileType: "file"}) } func (s *sAppWorkOrder) List(ctx context.Context, param *model.AppWorkOrderListParam) (items []*model.AppWorkOrderModel, total int, err error) { if err = appEnsureFeatureAccess(ctx, "workorder:list"); err != nil { return nil, 0, err } if param == nil { param = &model.AppWorkOrderListParam{} } if param.Page <= 0 { param.Page = 1 } if param.PageSize <= 0 { param.PageSize = 20 } if param.PageSize > 100 { param.PageSize = 100 } user, err := appCurrentUser(ctx) if err != nil { return nil, 0, err } mod := s.ownerModel(ctx, user) if param.CurrentStatus > 0 { mod = mod.Where(dao.TWoMain.Columns().CurrentStatus, param.CurrentStatus) } if param.WoType > 0 { mod = mod.Where(dao.TWoMain.Columns().WoType, param.WoType) } keyword := strings.TrimSpace(param.Keyword) if keyword == "" { keyword = strings.TrimSpace(param.PageInfo.Keyword) } if keyword != "" { like := "%" + keyword + "%" columns := dao.TWoMain.Columns() mod = mod.Where( "("+columns.WoNo+" LIKE ? OR "+columns.Title+" LIKE ? OR "+columns.Content+" LIKE ?)", like, like, like, ) } total, err = mod.Count() if err != nil { return nil, 0, gerror.Wrap(err, "查询工单数量失败") } if total == 0 { return []*model.AppWorkOrderModel{}, 0, nil } var rows []*entity.TWoMain if err = mod. OrderDesc(dao.TWoMain.Columns().CreatedAt). OrderDesc(dao.TWoMain.Columns().Id). Page(param.Page, param.PageSize). Scan(&rows); err != nil { return nil, 0, gerror.Wrap(err, "查询工单列表失败") } items = make([]*model.AppWorkOrderModel, 0, len(rows)) for _, row := range rows { if row != nil { items = append(items, appWorkOrderToModel(row)) } } return items, total, nil } func (s *sAppWorkOrder) Detail(ctx context.Context, param *model.AppWorkOrderDetailParam) (res *model.AppWorkOrderModel, err error) { if err = appEnsureFeatureAccess(ctx, "workorder:detail"); err != nil { return nil, err } if param == nil || strings.TrimSpace(param.WoNo) == "" { return nil, gerror.New("工单编号不能为空") } user, err := appCurrentUser(ctx) if err != nil { return nil, err } var row *entity.TWoMain if err = s.ownerModel(ctx, user). Where(dao.TWoMain.Columns().WoNo, strings.TrimSpace(param.WoNo)). Limit(1). Scan(&row); err != nil { return nil, gerror.Wrap(err, "查询工单详情失败") } if row == nil || row.Id == 0 { return nil, gerror.New("工单不存在或无权查看") } res = appWorkOrderToModel(row) var communications []*entity.TWoCommunication if err = dao.TWoCommunication.Ctx(ctx). Where(dao.TWoCommunication.Columns().TenantId, user.TenantId). Where(dao.TWoCommunication.Columns().WoNo, row.WoNo). OrderAsc(dao.TWoCommunication.Columns().Id). Scan(&communications); err != nil { return nil, gerror.Wrap(err, "查询工单沟通记录失败") } res.Communications = appWorkOrderCommunicationsToModels(communications) return res, nil } func (s *sAppWorkOrder) Create(ctx context.Context, param *model.AppWorkOrderCreateParam) (res *model.AppWorkOrderModel, err error) { if err = appEnsureFeatureAccess(ctx, "workorder:create"); err != nil { return nil, err } if param == nil { return nil, gerror.New("工单参数不能为空") } title := strings.TrimSpace(param.Title) content := strings.TrimSpace(param.Content) if title == "" { return nil, gerror.New("工单标题不能为空") } if content == "" { return nil, gerror.New("问题描述不能为空") } if param.WoType < 0 || param.WoType > 5 { return nil, gerror.New("工单类型不正确") } user, err := appCurrentUser(ctx) if err != nil { return nil, err } now := gtime.Now() woNo := appCardBatchNo("WO", user.UserId, now) createdDept := user.DeptId if createdDept <= 0 { createdDept = consts.AppTerminalDeptId } creatorType := appWorkOrderCreatorEnterprise if appIsTerminalUser(ctx, user) { creatorType = appWorkOrderCreatorTerminal } creatorName := strings.TrimSpace(appFirstNotEmpty(user.NickName, user.UserName)) handlerDeptId, handlerUserId, err := appWorkOrderHandler(ctx, user, createdDept) if err != nil { return nil, err } err = dao.TWoMain.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { _, err := dao.TWoMain.Ctx(ctx).TX(tx).Data(do.TWoMain{ WoNo: woNo, WoType: param.WoType, RelatedId: param.RelatedId, Title: title, Content: content, Attachment: firstAttachment(param.AttachmentUrls), CreatorType: creatorType, CreatorDeptId: createdDept, CreatorUserId: user.UserId, CreatorName: creatorName, CreatorPhone: strings.TrimSpace(user.Phonenumber), AssignDeptId: handlerDeptId, AssignUserId: handlerUserId, CurrentStatus: appWorkOrderStatusProcessing, HandlerDeptId: handlerDeptId, HandlerUserId: handlerUserId, TenantId: user.TenantId, CreatedDept: createdDept, CreatedBy: user.UserId, CreatedAt: now, UpdatedBy: user.UserId, UpdatedAt: now, }).Insert() if err != nil { return gerror.Wrap(err, "创建工单失败") } communicationData := do.TWoCommunication{ WoNo: woNo, CommType: 1, SenderDeptId: createdDept, SenderUserId: user.UserId, SenderName: creatorName, Content: content, TenantId: user.TenantId, CreatedDept: createdDept, CreatedBy: user.UserId, CreatedAt: now, UpdatedBy: user.UserId, UpdatedAt: now, } if len(param.AttachmentUrls) > 0 { communicationData.AttachmentUrls = gjson.New(param.AttachmentUrls) } if _, err = dao.TWoCommunication.Ctx(ctx).TX(tx).Data(communicationData).Insert(); err != nil { return gerror.Wrap(err, "创建工单沟通记录失败") } if _, err = dao.TWoRecord.Ctx(ctx).TX(tx).Data(do.TWoRecord{ WoNo: woNo, OperationType: appWorkOrderOperationCreate, OperationDeptId: createdDept, OperationUserId: user.UserId, BeforeStatus: 0, AfterStatus: appWorkOrderStatusProcessing, TenantId: user.TenantId, CreatedDept: createdDept, CreatedBy: user.UserId, CreatedAt: now, UpdatedBy: user.UserId, UpdatedAt: now, }).Insert(); err != nil { return gerror.Wrap(err, "创建工单操作记录失败") } return nil }) if err != nil { return nil, err } return s.Detail(ctx, &model.AppWorkOrderDetailParam{WoNo: woNo}) } func (s *sAppWorkOrder) ownerModel(ctx context.Context, user *entity.SysUser) *gdb.Model { columns := dao.TWoMain.Columns() mod := dao.TWoMain.Ctx(ctx). Where(columns.TenantId, user.TenantId) if appIsTerminalUser(ctx, user) { return mod.Where(columns.CreatorUserId, user.UserId) } return appApplyEnterpriseDataScope( ctx, user, mod, columns.CreatorDeptId, columns.CreatorUserId, func(legacyMod *gdb.Model) *gdb.Model { return legacyMod.Where( "("+columns.CreatorDeptId+" = ? OR "+columns.CreatorUserId+" = ?)", user.DeptId, user.UserId, ) }, ) } func appWorkOrderHandler(ctx context.Context, user *entity.SysUser, createdDept int64) (deptId int64, userId int64, err error) { if user == nil { return createdDept, 0, nil } var topDept *entity.SysDept if err = dao.SysDept.Ctx(ctx). Where(dao.SysDept.Columns().TenantId, user.TenantId). Where(dao.SysDept.Columns().ParentId, 0). OrderAsc(dao.SysDept.Columns().OrderNum). OrderAsc(dao.SysDept.Columns().DeptId). Limit(1). Scan(&topDept); err != nil { return 0, 0, gerror.Wrap(err, "查询顶级工单处理部门失败") } if topDept != nil && topDept.DeptId > 0 { return topDept.DeptId, topDept.Leader, nil } deptId = createdDept if deptId <= 0 { deptId = appUserDeptOrDefault(user) } var dept *entity.SysDept if err = dao.SysDept.Ctx(ctx). Where(dao.SysDept.Columns().TenantId, user.TenantId). Where(dao.SysDept.Columns().DeptId, deptId). Limit(1). Scan(&dept); err != nil { return 0, 0, gerror.Wrap(err, "查询工单处理部门失败") } if dept != nil && dept.Leader > 0 { userId = dept.Leader } return deptId, userId, nil } func appWorkOrderToModel(row *entity.TWoMain) *model.AppWorkOrderModel { if row == nil { return nil } return &model.AppWorkOrderModel{ Id: row.Id, WoNo: row.WoNo, WoType: row.WoType, WoTypeText: appWorkOrderTypeText(row.WoType), RelatedId: row.RelatedId, Title: row.Title, Content: row.Content, AttachmentUrls: decodeAttachmentUrls(row.Attachment), CreatorType: row.CreatorType, CreatorName: row.CreatorName, CreatorPhone: row.CreatorPhone, CurrentStatus: row.CurrentStatus, CurrentStatusText: appWorkOrderStatusText(row.CurrentStatus), StatusType: appWorkOrderStatusType(row.CurrentStatus), CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt, } } func appWorkOrderCommunicationsToModels(rows []*entity.TWoCommunication) []*model.AppWorkOrderCommModel { items := make([]*model.AppWorkOrderCommModel, 0, len(rows)) for _, row := range rows { if row == nil { continue } items = append(items, &model.AppWorkOrderCommModel{ Id: row.Id, WoNo: row.WoNo, CommType: row.CommType, CommTypeText: appWorkOrderCommTypeText(row.CommType), SenderName: row.SenderName, Content: row.Content, AttachmentUrls: row.AttachmentUrls, CreatedAt: row.CreatedAt, }) } return items } func firstAttachment(urls []string) string { if len(urls) == 0 { return "" } return strings.TrimSpace(urls[0]) } func decodeAttachmentUrls(value string) []string { value = strings.TrimSpace(value) if value == "" { return nil } var urls []string if err := gjson.DecodeTo(value, &urls); err == nil && len(urls) > 0 { return urls } return []string{value} } func appWorkOrderTypeText(value int) string { switch value { case 0: return "卡片" case 1: return "模组" case 2: return "设备" case 3: return "订单" case 4: return "余额" case 5: return "提现" default: return "其他" } } func appWorkOrderStatusText(value int) string { switch value { case appWorkOrderStatusAssigned: return "已分派" case appWorkOrderStatusProcessing: return "处理中" case appWorkOrderStatusResolved: return "已解决" case appWorkOrderStatusClosed: return "已完结" case appWorkOrderStatusCanceled: return "已取消" default: return "待处理" } } func appWorkOrderStatusType(value int) string { switch value { case appWorkOrderStatusResolved, appWorkOrderStatusClosed: return "success" case appWorkOrderStatusCanceled: return "danger" case appWorkOrderStatusProcessing: return "primary" default: return "warning" } } func appWorkOrderCommTypeText(value int) string { if value == 1 { return "反馈" } return "回复" }