Files
backend-go/controllers/project_orders_controller.go
2026-06-06 13:50:56 +08:00

429 lines
12 KiB
Go
Raw Permalink 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 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,
})
}