chore: 初始开源发布
iotlcm-app:小程序端 + 用户端服务。 - app/:uni-app 小程序前端(基于 unibest,MIT) - 微信 AppID → YOUR_WX_APPID,uni-app AppID → __UNI__YOURAPPID - 内网后端地址 → admin.iotlcm.example.com - 应用标题与 unibest 模板 README 重写(保留上游致谢与 MIT 声明) - server/:GoFrame 用户端服务(基于 XiuAdmin,Apache-2.0) - module xiuadmin → git.5iot.com/iotlcm-public/iotlcm-app(360 文件) - 支付回调地址 → app.iotlcm.example.com,商户名与应用名泛化 - 真实手机号 → 合法 11 位编造号(保留号段前 3 位 + 后 4 位) - 依赖号码尾部的断言同步更新 - 补 server/LICENSE(Apache-2.0) - 移除内部资产:CI 触发器 - 补根 README,LF 行尾 + .gitattributes 已知:TestSysDept_RefreshDeptAncestors 在源仓同一提交上同样失败,属既有缺陷。
This commit is contained in:
@@ -0,0 +1,448 @@
|
||||
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 "回复"
|
||||
}
|
||||
Reference in New Issue
Block a user