package financeapplication import ( "errors" "strconv" "strings" "hyapp-admin-server/internal/middleware" "hyapp-admin-server/internal/model" "hyapp-admin-server/internal/modules/shared" "hyapp-admin-server/internal/repository" "hyapp-admin-server/internal/response" "github.com/gin-gonic/gin" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "gorm.io/gorm" ) type Handler struct { service *Service audit shared.OperationLogger } func New(store *repository.Store, audit shared.OperationLogger, options ...Option) *Handler { return &Handler{service: NewService(store, options...), audit: audit} } func (h *Handler) ListApplications(c *gin.Context) { options := shared.ListOptions(c) items, total, err := h.service.ListApplications(repository.FinanceApplicationListOptions{ Page: options.Page, PageSize: options.PageSize, AppCode: firstQuery(c, "app_code", "appCode"), Operation: firstQuery(c, "operation"), Status: options.Status, Keyword: options.Keyword, }) if err != nil { response.ServerError(c, "获取财务申请列表失败") return } response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: total}) } func (h *Handler) ListMyApplications(c *gin.Context) { options := shared.ListOptions(c) items, total, err := h.service.ListMyApplications(shared.ActorFromContext(c), repository.FinanceApplicationListOptions{ Page: options.Page, PageSize: options.PageSize, AppCode: firstQuery(c, "app_code", "appCode"), Operation: firstQuery(c, "operation"), Status: options.Status, Keyword: options.Keyword, }) if err != nil { writeServiceError(c, err, "获取我的财务申请列表失败") return } response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: total}) } func (h *Handler) CreateApplication(c *gin.Context) { var req createApplicationRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "财务申请参数不正确") return } item, err := h.service.CreateApplication(c.Request.Context(), shared.ActorFromContext(c), req) if err != nil { writeServiceError(c, err, "发起财务申请失败") return } shared.OperationLogWithResourceID(c, h.audit, "create-finance-application", "admin_finance_applications", idString(item.ID), "success", item.Operation) response.Created(c, item) } func (h *Handler) ApproveApplication(c *gin.Context) { h.auditApplication(c, model.FinanceApplicationStatusApproved) } func (h *Handler) RejectApplication(c *gin.Context) { h.auditApplication(c, model.FinanceApplicationStatusRejected) } func (h *Handler) AuditApplication(c *gin.Context) { var req struct { Decision string `json:"decision"` AuditRemark string `json:"auditRemark"` } if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "审核参数不正确") return } switch strings.TrimSpace(req.Decision) { case model.FinanceApplicationStatusApproved: h.auditApplicationWithRemark(c, model.FinanceApplicationStatusApproved, req.AuditRemark) case model.FinanceApplicationStatusRejected: h.auditApplicationWithRemark(c, model.FinanceApplicationStatusRejected, req.AuditRemark) default: response.BadRequest(c, "审核结果不正确") } } func (h *Handler) auditApplication(c *gin.Context, decision string) { var req auditApplicationRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "审核参数不正确") return } h.auditApplicationWithRemark(c, decision, req.AuditRemark) } func (h *Handler) auditApplicationWithRemark(c *gin.Context, decision string, remark string) { id, ok := shared.ParseID(c, "application_id") if !ok { return } var ( item *applicationDTO err error ) if decision == model.FinanceApplicationStatusApproved { item, err = h.service.ApproveApplication(c.Request.Context(), shared.ActorFromContext(c), id, remark, middleware.CurrentRequestID(c)) } else { item, err = h.service.RejectApplication(c.Request.Context(), shared.ActorFromContext(c), id, remark, middleware.CurrentRequestID(c)) } if err != nil { writeServiceError(c, err, "审核财务申请失败") return } shared.OperationLogWithResourceID(c, h.audit, "audit-finance-application", "admin_finance_applications", idString(item.ID), "success", decision) response.OK(c, item) } func writeServiceError(c *gin.Context, err error, fallback string) { if errors.Is(err, gorm.ErrRecordNotFound) { response.NotFound(c, "财务申请不存在") return } if errors.Is(err, repository.ErrFinanceApplicationAlreadyAudited) { response.BadRequest(c, "申请已审核") return } switch err.Error() { case "没有操作权限": response.Forbidden(c, err.Error()) case "admin store is not configured", "finance wallet executor is not configured": response.ServerError(c, fallback) default: if status.Code(err) == codes.NotFound { response.NotFound(c, err.Error()) return } if st, ok := status.FromError(err); ok && strings.TrimSpace(st.Message()) != "" { switch st.Code() { case codes.PermissionDenied: response.Forbidden(c, st.Message()) return case codes.InvalidArgument, codes.FailedPrecondition, codes.AlreadyExists, codes.Aborted, codes.ResourceExhausted: response.BadRequest(c, st.Message()) return } } response.BadRequest(c, err.Error()) } } func firstQuery(c *gin.Context, keys ...string) string { for _, key := range keys { if value := strings.TrimSpace(c.Query(key)); value != "" { return value } } return "" } func idString(id uint) string { return strconv.FormatUint(uint64(id), 10) }