429 lines
12 KiB
Go
429 lines
12 KiB
Go
package controllers
|
||
|
||
import (
|
||
"net/http"
|
||
"strconv"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
"git.beifan.cn/trace-system/backend-go/models"
|
||
"git.beifan.cn/trace-system/backend-go/services"
|
||
)
|
||
|
||
// ProjectOrdersController 项目工单控制器
|
||
type ProjectOrdersController struct {
|
||
projectOrdersService services.ProjectOrdersService
|
||
}
|
||
|
||
// NewProjectOrdersController 创建项目工单控制器实例
|
||
func NewProjectOrdersController() *ProjectOrdersController {
|
||
return &ProjectOrdersController{
|
||
projectOrdersService: services.ProjectOrdersService{},
|
||
}
|
||
}
|
||
|
||
// Create 创建项目工单
|
||
// @Summary 创建项目工单
|
||
// @Description 创建一个新的项目工单并分配编号
|
||
// @Tags 项目工单
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param data body models.CreateProjectOrderDTO true "工单数据"
|
||
// @Success 200 {object} models.DataResponse
|
||
// @Failure 400 {object} models.ErrorResponse
|
||
// @Failure 401 {object} models.ErrorResponse
|
||
// @Failure 500 {object} models.ErrorResponse
|
||
// @Router /project-orders [post]
|
||
func (c *ProjectOrdersController) Create(ctx *gin.Context) {
|
||
userModel, ok := GetCurrentUser(ctx)
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
var dto models.CreateProjectOrderDTO
|
||
if !BindJSON(ctx, &dto) {
|
||
return
|
||
}
|
||
|
||
order, err := c.projectOrdersService.Create(dto, userModel.ID)
|
||
if err != nil {
|
||
ErrorResponse(ctx, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
SuccessResponse(ctx, "项目工单创建成功", gin.H{
|
||
"order": order,
|
||
})
|
||
}
|
||
|
||
// FindAll 获取项目工单列表
|
||
// @Summary 获取项目工单列表
|
||
// @Description 支持分页、搜索、按状态/服务类型/技术员筛选
|
||
// @Tags 项目工单
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param page query int false "页码"
|
||
// @Param limit query int false "每页数量"
|
||
// @Param search query string false "搜索关键词"
|
||
// @Param workOrderStatus query string false "工单状态"
|
||
// @Param serviceType query string false "服务类型"
|
||
// @Param technicianId query int false "技术员 ID"
|
||
// @Param mine query bool false "仅查看自己负责的工单"
|
||
// @Success 200 {object} models.PaginationResponse
|
||
// @Failure 401 {object} models.ErrorResponse
|
||
// @Failure 500 {object} models.ErrorResponse
|
||
// @Router /project-orders [get]
|
||
func (c *ProjectOrdersController) FindAll(ctx *gin.Context) {
|
||
userModel, ok := GetCurrentUser(ctx)
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
|
||
limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "20"))
|
||
search := ctx.DefaultQuery("search", "")
|
||
workOrderStatus := ctx.DefaultQuery("workOrderStatus", "")
|
||
serviceType := ctx.DefaultQuery("serviceType", "")
|
||
|
||
var technicianID *uint
|
||
if tidStr := ctx.Query("technicianId"); tidStr != "" {
|
||
if tid, err := strconv.ParseUint(tidStr, 10, 32); err == nil {
|
||
t := uint(tid)
|
||
technicianID = &t
|
||
}
|
||
}
|
||
// 非管理员默认只看自己的工单(除非显式指定 technicianId)
|
||
if userModel.Role != "admin" && technicianID == nil {
|
||
technicianID = &userModel.ID
|
||
}
|
||
// mine=true 强制只看自己的
|
||
if ctx.Query("mine") == "true" {
|
||
technicianID = &userModel.ID
|
||
}
|
||
|
||
orders, total, totalPages, err := c.projectOrdersService.FindAll(page, limit, search, workOrderStatus, serviceType, technicianID)
|
||
if err != nil {
|
||
ErrorResponse(ctx, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
|
||
SuccessResponse(ctx, "获取项目工单列表成功", gin.H{
|
||
"data": orders,
|
||
"pagination": gin.H{
|
||
"page": page,
|
||
"limit": limit,
|
||
"total": total,
|
||
"totalPages": totalPages,
|
||
},
|
||
})
|
||
}
|
||
|
||
// FindOne 获取单个项目工单详情
|
||
// @Summary 获取项目工单详情
|
||
// @Tags 项目工单
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param serialNumber path string true "工单号"
|
||
// @Success 200 {object} models.DataResponse
|
||
// @Failure 401 {object} models.ErrorResponse
|
||
// @Failure 404 {object} models.ErrorResponse
|
||
// @Router /project-orders/{serialNumber} [get]
|
||
func (c *ProjectOrdersController) FindOne(ctx *gin.Context) {
|
||
serialNumber := ctx.Param("serialNumber")
|
||
|
||
order, err := c.projectOrdersService.FindOne(serialNumber)
|
||
if err != nil {
|
||
ErrorResponse(ctx, http.StatusNotFound, err.Error())
|
||
return
|
||
}
|
||
|
||
SuccessResponse(ctx, "查询成功", gin.H{
|
||
"order": order,
|
||
})
|
||
}
|
||
|
||
// Update 更新项目工单信息
|
||
// @Summary 更新项目工单
|
||
// @Tags 项目工单
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param serialNumber path string true "工单号"
|
||
// @Param data body models.UpdateProjectOrderDTO true "更新数据"
|
||
// @Success 200 {object} models.DataResponse
|
||
// @Failure 400 {object} models.ErrorResponse
|
||
// @Failure 401 {object} models.ErrorResponse
|
||
// @Failure 403 {object} models.ErrorResponse
|
||
// @Router /project-orders/{serialNumber} [patch]
|
||
func (c *ProjectOrdersController) Update(ctx *gin.Context) {
|
||
userModel, ok := GetCurrentUser(ctx)
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
serialNumber := ctx.Param("serialNumber")
|
||
var dto models.UpdateProjectOrderDTO
|
||
if !BindJSON(ctx, &dto) {
|
||
return
|
||
}
|
||
|
||
order, err := c.projectOrdersService.Update(serialNumber, dto, userModel)
|
||
if err != nil {
|
||
ErrorResponse(ctx, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
|
||
SuccessResponse(ctx, "工单更新成功", gin.H{
|
||
"order": order,
|
||
})
|
||
}
|
||
|
||
// SubmitCompletion 技术员提交完成资料
|
||
// @Summary 提交完成确认
|
||
// @Description 技术员填写处理结果后提交,工单进入"待完成确认"状态
|
||
// @Tags 项目工单
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param serialNumber path string true "工单号"
|
||
// @Param data body models.SubmitProjectCompletionDTO true "处理结果"
|
||
// @Success 200 {object} models.DataResponse
|
||
// @Failure 400 {object} models.ErrorResponse
|
||
// @Failure 401 {object} models.ErrorResponse
|
||
// @Router /project-orders/{serialNumber}/submit [post]
|
||
func (c *ProjectOrdersController) SubmitCompletion(ctx *gin.Context) {
|
||
userModel, ok := GetCurrentUser(ctx)
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
serialNumber := ctx.Param("serialNumber")
|
||
var dto models.SubmitProjectCompletionDTO
|
||
if !BindJSON(ctx, &dto) {
|
||
return
|
||
}
|
||
|
||
order, err := c.projectOrdersService.SubmitCompletion(serialNumber, dto, userModel)
|
||
if err != nil {
|
||
ErrorResponse(ctx, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
|
||
SuccessResponse(ctx, "已提交完成确认", gin.H{
|
||
"order": order,
|
||
})
|
||
}
|
||
|
||
// GenerateQRCode 生成项目工单二维码
|
||
// @Summary 生成项目工单二维码
|
||
// @Tags 项目工单
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param serialNumber path string true "工单号"
|
||
// @Param data body models.QRCodeDTO false "二维码参数"
|
||
// @Success 200 {object} models.QRCodeResponse
|
||
// @Failure 400 {object} models.ErrorResponse
|
||
// @Failure 401 {object} models.ErrorResponse
|
||
// @Router /project-orders/{serialNumber}/qrcode [post]
|
||
func (c *ProjectOrdersController) GenerateQRCode(ctx *gin.Context) {
|
||
serialNumber := ctx.Param("serialNumber")
|
||
|
||
var qrCodeData models.QRCodeDTO
|
||
if !BindJSON(ctx, &qrCodeData) {
|
||
return
|
||
}
|
||
|
||
protocol := "http"
|
||
if ctx.Request.TLS != nil {
|
||
protocol = "https"
|
||
}
|
||
|
||
qrCodeBase64, queryUrl, err := c.projectOrdersService.GenerateQRCode(
|
||
serialNumber,
|
||
qrCodeData.BaseUrl,
|
||
ctx.Request.Host,
|
||
protocol,
|
||
)
|
||
if err != nil {
|
||
ErrorResponse(ctx, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
|
||
SuccessResponse(ctx, "二维码生成成功", gin.H{
|
||
"qrCodeData": qrCodeBase64,
|
||
"queryUrl": queryUrl,
|
||
})
|
||
}
|
||
|
||
// Reassign 重新分配技术员(仅管理员)
|
||
// @Summary 重新分配技术员
|
||
// @Tags 项目工单
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param serialNumber path string true "工单号"
|
||
// @Param data body models.ReassignProjectOrderDTO true "新技术员 ID"
|
||
// @Success 200 {object} models.DataResponse
|
||
// @Failure 400 {object} models.ErrorResponse
|
||
// @Failure 401 {object} models.ErrorResponse
|
||
// @Router /project-orders/{serialNumber}/reassign [post]
|
||
func (c *ProjectOrdersController) Reassign(ctx *gin.Context) {
|
||
serialNumber := ctx.Param("serialNumber")
|
||
var dto models.ReassignProjectOrderDTO
|
||
if !BindJSON(ctx, &dto) {
|
||
return
|
||
}
|
||
|
||
order, err := c.projectOrdersService.Reassign(serialNumber, dto.TechnicianID)
|
||
if err != nil {
|
||
ErrorResponse(ctx, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
|
||
SuccessResponse(ctx, "重新分配成功", gin.H{
|
||
"order": order,
|
||
})
|
||
}
|
||
|
||
// ForceClose 强制关闭工单(仅管理员)
|
||
// @Summary 强制关闭工单
|
||
// @Tags 项目工单
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param serialNumber path string true "工单号"
|
||
// @Success 200 {object} models.DataResponse
|
||
// @Failure 400 {object} models.ErrorResponse
|
||
// @Failure 401 {object} models.ErrorResponse
|
||
// @Router /project-orders/{serialNumber}/force-close [post]
|
||
func (c *ProjectOrdersController) ForceClose(ctx *gin.Context) {
|
||
serialNumber := ctx.Param("serialNumber")
|
||
|
||
order, err := c.projectOrdersService.ForceClose(serialNumber)
|
||
if err != nil {
|
||
ErrorResponse(ctx, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
|
||
SuccessResponse(ctx, "工单已强制关闭", gin.H{
|
||
"order": order,
|
||
})
|
||
}
|
||
|
||
// Delete 删除项目工单(仅管理员)
|
||
// @Summary 删除项目工单
|
||
// @Tags 项目工单
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param serialNumber path string true "工单号"
|
||
// @Success 200 {object} models.BaseResponse
|
||
// @Failure 400 {object} models.ErrorResponse
|
||
// @Failure 401 {object} models.ErrorResponse
|
||
// @Router /project-orders/{serialNumber} [delete]
|
||
func (c *ProjectOrdersController) Delete(ctx *gin.Context) {
|
||
serialNumber := ctx.Param("serialNumber")
|
||
|
||
if err := c.projectOrdersService.Delete(serialNumber); err != nil {
|
||
ErrorResponse(ctx, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
|
||
SuccessResponse(ctx, "项目工单删除成功")
|
||
}
|
||
|
||
// PublicQuery 公开查询项目工单(无需登录,脱敏)
|
||
// @Summary 公开查询项目工单
|
||
// @Tags 项目工单查询
|
||
// @Produce json
|
||
// @Param serialNumber path string true "工单号"
|
||
// @Success 200 {object} models.DataResponse
|
||
// @Failure 404 {object} models.ErrorResponse
|
||
// @Router /project-orders/{serialNumber}/query [get]
|
||
func (c *ProjectOrdersController) PublicQuery(ctx *gin.Context) {
|
||
serialNumber := ctx.Param("serialNumber")
|
||
|
||
view, err := c.projectOrdersService.PublicQuery(serialNumber)
|
||
if err != nil {
|
||
ErrorResponse(ctx, http.StatusNotFound, err.Error())
|
||
return
|
||
}
|
||
|
||
SuccessResponse(ctx, "查询成功", gin.H{
|
||
"order": view,
|
||
})
|
||
}
|
||
|
||
// EngineerComplete 项目完成提交
|
||
// @Summary 工程师提交完成
|
||
// @Description 工程师上传现场图片后签字提交,工单进入已完成状态
|
||
// @Tags 项目工单查询
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param serialNumber path string true "工单号"
|
||
// @Param data body models.ProjectEngineerCompleteDTO true "确认数据"
|
||
// @Success 200 {object} models.DataResponse
|
||
// @Failure 400 {object} models.ErrorResponse
|
||
// @Failure 401 {object} models.ErrorResponse
|
||
// @Failure 429 {object} models.ErrorResponse
|
||
// @Router /project-orders/{serialNumber}/complete [post]
|
||
func (c *ProjectOrdersController) EngineerComplete(ctx *gin.Context) {
|
||
serialNumber := ctx.Param("serialNumber")
|
||
|
||
var dto models.ProjectEngineerCompleteDTO
|
||
if !BindJSON(ctx, &dto) {
|
||
return
|
||
}
|
||
|
||
view, err := c.projectOrdersService.EngineerComplete(serialNumber, dto)
|
||
if err != nil {
|
||
// 频率限制单独返回 429
|
||
if err.Error() == "操作过于频繁,请稍后再试" {
|
||
ErrorResponse(ctx, http.StatusTooManyRequests, err.Error())
|
||
return
|
||
}
|
||
ErrorResponse(ctx, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
|
||
SuccessResponse(ctx, "提交成功", gin.H{
|
||
"order": view,
|
||
})
|
||
}
|
||
|
||
// UploadSiteImages 上传完成确认现场图片
|
||
// @Summary 上传项目现场图片
|
||
// @Tags 项目工单查询
|
||
// @Accept multipart/form-data
|
||
// @Produce json
|
||
// @Param serialNumber path string true "工单号"
|
||
// @Param files formData file true "现场图片"
|
||
// @Success 200 {object} models.DataResponse
|
||
// @Failure 400 {object} models.ErrorResponse
|
||
// @Failure 404 {object} models.ErrorResponse
|
||
// @Router /project-orders/{serialNumber}/site-images [post]
|
||
func (c *ProjectOrdersController) UploadSiteImages(ctx *gin.Context) {
|
||
serialNumber := ctx.Param("serialNumber")
|
||
|
||
form, err := ctx.MultipartForm()
|
||
if err != nil {
|
||
ErrorResponse(ctx, http.StatusBadRequest, "请选择要上传的现场图片")
|
||
return
|
||
}
|
||
|
||
files := form.File["files"]
|
||
if len(files) == 0 {
|
||
files = form.File["file"]
|
||
}
|
||
|
||
images, err := c.projectOrdersService.UploadSiteImages(serialNumber, files)
|
||
if err != nil {
|
||
ErrorResponse(ctx, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
|
||
SuccessResponse(ctx, "现场图片上传成功", gin.H{
|
||
"siteImages": images,
|
||
})
|
||
}
|