2026-07-27 03:18:41 +08:00
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"
)
2026-07-27 09:14:36 +08:00
var errApprovalNotProcessable = errors . New ( "approval is not processable" )
2026-07-27 03:18:41 +08:00
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
}
2026-07-27 09:14:36 +08:00
if request . Status != "approved" && request . Status != "rejected" {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
2026-07-27 03:18:41 +08:00
values := approvalValues ( request . Status , request . Opinion , claims . Identity )
2026-07-27 14:28:23 +08:00
var approval models . AuditApproval
2026-07-27 03:18:41 +08:00
err = impl . DBService . Transaction ( func ( transaction * gorm . DB ) error {
if err := transaction . Where ( "identity = ?" , ctx . Param ( "identity" ) ) . First ( & approval ) . Error ; err != nil {
return err
}
2026-07-27 09:14:36 +08:00
if approval . Status != "pending" || approval . ApplicantIdentity == claims . Identity {
return errApprovalNotProcessable
}
2026-07-27 03:18:41 +08:00
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
}
2026-07-27 14:28:23 +08:00
if result := transaction . Model ( & models . AuditApproval { } ) . Where ( "identity = ? AND status = ?" , approval . Identity , "pending" ) . Updates ( values ) ; result . Error != nil {
2026-07-27 03:18:41 +08:00
return result . Error
} else if result . RowsAffected == 0 {
2026-07-27 09:14:36 +08:00
return errApprovalNotProcessable
2026-07-27 03:18:41 +08:00
}
after , err := json . Marshal ( values )
if err != nil {
return err
}
2026-07-27 14:28:23 +08:00
return transaction . Create ( & models . AuditOperationLog {
2026-07-27 03:18:41 +08:00
Entity : newEntity ( "enabled" ) ,
OperatorIdentity : claims . Identity ,
Action : "approve" ,
2026-07-27 14:28:23 +08:00
ObjectType : "audit_approval" ,
2026-07-27 03:18:41 +08:00
ObjectIdentity : approval . Identity ,
BeforeData : string ( before ) ,
AfterData : string ( after ) ,
} ) . Error
} )
if err != nil {
2026-07-27 09:14:36 +08:00
if errors . Is ( err , errApprovalNotProcessable ) {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
2026-07-27 03:18:41 +08:00
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 ) )
}