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" ) // AftersalesController 售后工单控制器 type AftersalesController struct { aftersalesService services.AftersalesService } // NewAftersalesController 创建售后工单控制器实例 func NewAftersalesController() *AftersalesController { return &AftersalesController{ aftersalesService: services.AftersalesService{}, } } // Create 创建售后工单 // @Summary 创建售后工单 // @Description 创建一个新的售后工单并分配编号 // @Tags 售后工单 // @Accept json // @Produce json // @Security BearerAuth // @Param data body models.CreateAftersalesOrderDTO true "工单数据" // @Success 200 {object} models.DataResponse // @Failure 400 {object} models.ErrorResponse // @Failure 401 {object} models.ErrorResponse // @Failure 500 {object} models.ErrorResponse // @Router /aftersales [post] func (c *AftersalesController) Create(ctx *gin.Context) { userModel, ok := GetCurrentUser(ctx) if !ok { return } var dto models.CreateAftersalesOrderDTO if !BindJSON(ctx, &dto) { return } order, err := c.aftersalesService.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 /aftersales [get] func (c *AftersalesController) 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.aftersalesService.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 /aftersales/{serialNumber} [get] func (c *AftersalesController) FindOne(ctx *gin.Context) { serialNumber := ctx.Param("serialNumber") order, err := c.aftersalesService.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.UpdateAftersalesOrderDTO true "更新数据" // @Success 200 {object} models.DataResponse // @Failure 400 {object} models.ErrorResponse // @Failure 401 {object} models.ErrorResponse // @Failure 403 {object} models.ErrorResponse // @Router /aftersales/{serialNumber} [patch] func (c *AftersalesController) Update(ctx *gin.Context) { userModel, ok := GetCurrentUser(ctx) if !ok { return } serialNumber := ctx.Param("serialNumber") var dto models.UpdateAftersalesOrderDTO if !BindJSON(ctx, &dto) { return } order, err := c.aftersalesService.Update(serialNumber, dto, userModel) if err != nil { ErrorResponse(ctx, http.StatusBadRequest, err.Error()) return } SuccessResponse(ctx, "工单更新成功", gin.H{ "order": order, }) } // SubmitForConfirmation 技术员提交客户确认 // @Summary 提交客户确认 // @Description 技术员填写处理结果后提交,工单进入"待客户确认"状态 // @Tags 售后工单 // @Accept json // @Produce json // @Security BearerAuth // @Param serialNumber path string true "工单号" // @Param data body models.SubmitForConfirmationDTO true "处理结果" // @Success 200 {object} models.DataResponse // @Failure 400 {object} models.ErrorResponse // @Failure 401 {object} models.ErrorResponse // @Router /aftersales/{serialNumber}/submit [post] func (c *AftersalesController) SubmitForConfirmation(ctx *gin.Context) { userModel, ok := GetCurrentUser(ctx) if !ok { return } serialNumber := ctx.Param("serialNumber") var dto models.SubmitForConfirmationDTO if !BindJSON(ctx, &dto) { return } order, err := c.aftersalesService.SubmitForConfirmation(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 /aftersales/{serialNumber}/qrcode [post] func (c *AftersalesController) 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.aftersalesService.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.ReassignAftersalesDTO true "新技术员 ID" // @Success 200 {object} models.DataResponse // @Failure 400 {object} models.ErrorResponse // @Failure 401 {object} models.ErrorResponse // @Router /aftersales/{serialNumber}/reassign [post] func (c *AftersalesController) Reassign(ctx *gin.Context) { serialNumber := ctx.Param("serialNumber") var dto models.ReassignAftersalesDTO if !BindJSON(ctx, &dto) { return } order, err := c.aftersalesService.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 /aftersales/{serialNumber}/force-close [post] func (c *AftersalesController) ForceClose(ctx *gin.Context) { serialNumber := ctx.Param("serialNumber") order, err := c.aftersalesService.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 /aftersales/{serialNumber} [delete] func (c *AftersalesController) Delete(ctx *gin.Context) { serialNumber := ctx.Param("serialNumber") if err := c.aftersalesService.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 /aftersales/{serialNumber}/query [get] func (c *AftersalesController) PublicQuery(ctx *gin.Context) { serialNumber := ctx.Param("serialNumber") view, err := c.aftersalesService.PublicQuery(serialNumber) if err != nil { ErrorResponse(ctx, http.StatusNotFound, err.Error()) return } SuccessResponse(ctx, "查询成功", gin.H{ "order": view, }) } // CustomerConfirm 客户授权/未授权确认 // @Summary 客户授权确认 // @Description 客户输入手机号后四位后选择已授权或未授权 // @Tags 售后工单查询 // @Accept json // @Produce json // @Param serialNumber path string true "工单号" // @Param data body models.CustomerConfirmDTO true "确认数据" // @Success 200 {object} models.DataResponse // @Failure 400 {object} models.ErrorResponse // @Failure 401 {object} models.ErrorResponse // @Failure 429 {object} models.ErrorResponse // @Router /aftersales/{serialNumber}/confirm [post] func (c *AftersalesController) CustomerConfirm(ctx *gin.Context) { serialNumber := ctx.Param("serialNumber") var dto models.CustomerConfirmDTO if !BindJSON(ctx, &dto) { return } view, err := c.aftersalesService.CustomerConfirm(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, }) }