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, }) }