package platform import ( "encoding/json" "errors" "time" "git.apinb.com/bsm-sdk/core/errcode" "git.apinb.com/bsm-sdk/core/infra" "git.apinb.com/bsm-sdk/core/middleware" "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" "git.apinb.com/heqiapp/platforms/backend/api/internal/models" "github.com/gin-gonic/gin" "gorm.io/gorm" ) var errApprovalNotProcessable = errors.New("approval is not processable") func approvalValues(status, opinion, operatorIdentity string) map[string]any { return map[string]any{ "status": status, "opinion": opinion, "handler_identity": operatorIdentity, "handled_at": time.Now().UTC(), } } // ApproveAudit records the reviewer and decision without allowing an approval // to mutate any business fields. The operation audit is created atomically with // the approval update. func ApproveAudit(ctx *gin.Context) { claims, err := middleware.ParseAuth(ctx) if err != nil { infra.Response.Error(ctx, err) return } var request struct { Status string `json:"status" binding:"required,max=32"` Opinion string `json:"opinion" binding:"max=2000"` } if err := ctx.ShouldBindJSON(&request); err != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } if request.Status != "approved" && request.Status != "rejected" { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } values := approvalValues(request.Status, request.Opinion, claims.Identity) var approval models.AuditApproval err = impl.DBService.Transaction(func(transaction *gorm.DB) error { if err := transaction.Where("identity = ?", ctx.Param("identity")).First(&approval).Error; err != nil { return err } if approval.Status != "pending" || approval.ApplicantIdentity == claims.Identity { return errApprovalNotProcessable } before, err := json.Marshal(gin.H{ "status": approval.Status, "opinion": approval.Opinion, "handler_identity": approval.HandlerIdentity, "handled_at": approval.HandledAt, }) if err != nil { return err } if result := transaction.Model(&models.AuditApproval{}).Where("identity = ? AND status = ?", approval.Identity, "pending").Updates(values); result.Error != nil { return result.Error } else if result.RowsAffected == 0 { return errApprovalNotProcessable } after, err := json.Marshal(values) if err != nil { return err } return transaction.Create(&models.AuditOperationLog{ Entity: newEntity("enabled"), OperatorIdentity: claims.Identity, Action: "approve", ObjectType: "audit_approval", ObjectIdentity: approval.Identity, BeforeData: string(before), AfterData: string(after), }).Error }) if err != nil { if errors.Is(err, errApprovalNotProcessable) { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } if errors.Is(err, gorm.ErrRecordNotFound) { infra.Response.Error(ctx, errcode.ErrRecordNotFound) return } infra.Response.Error(ctx, err) return } approval.Status = request.Status approval.Opinion = request.Opinion approval.HandlerIdentity = claims.Identity handledAt := values["handled_at"].(time.Time) approval.HandledAt = &handledAt infra.Response.Success(ctx, resourceResponse(approval)) }