fix: resolve platform relation identities

This commit is contained in:
2026-07-27 10:11:07 +08:00
parent 98995d6968
commit 2e62238416
11 changed files with 437 additions and 80 deletions

View File

@@ -1,8 +1,10 @@
package platform
import (
"bytes"
"encoding/json"
"errors"
"io"
"reflect"
"strings"
@@ -33,6 +35,60 @@ func ResourceHandlers(model any, createFields, updateFields []string, relations
func(ctx *gin.Context) { updateResource(ctx, model, updateFields, relations) }
}
// FinSettlementHandlers accepts a public subject_identity and derives the
// polymorphic storage key from its declared subject_type.
func FinSettlementHandlers() (gin.HandlerFunc, gin.HandlerFunc, gin.HandlerFunc, gin.HandlerFunc) {
fields := []string{"settlement_no", "subject_type", "subject_id", "period_start", "period_end"}
return func(ctx *gin.Context) { listResource(ctx, &models.FinSettlement{}) },
func(ctx *gin.Context) {
if err := rewriteSettlementSubject(ctx); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
createResource(ctx, &models.FinSettlement{}, fields, nil)
},
func(ctx *gin.Context) { getResource(ctx, &models.FinSettlement{}) },
func(ctx *gin.Context) {
if err := rewriteSettlementSubject(ctx); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
updateResource(ctx, &models.FinSettlement{}, fields, nil)
}
}
func rewriteSettlementSubject(ctx *gin.Context) error {
var input map[string]any
if err := ctx.ShouldBindJSON(&input); err != nil {
return err
}
subjectType, _ := input["subject_type"].(string)
identity, _ := input["subject_identity"].(string)
var model any
switch subjectType {
case "gas", "gas_basic":
model = &models.GasBasic{}
case "delivery", "delivery_basic":
model = &models.DeliveryBasic{}
case "staff", "staff_account":
model = &models.StaffAccount{}
default:
return errors.New("invalid settlement subject")
}
id, err := resolveIdentityID(model, identity, true)
if err != nil {
return err
}
delete(input, "subject_identity")
input["subject_id"] = id
encoded, err := json.Marshal(input)
if err != nil {
return err
}
ctx.Request.Body = io.NopCloser(bytes.NewReader(encoded))
return nil
}
func listResource(ctx *gin.Context, model any) {
page, size := pageSize(ctx)
list := reflect.New(reflect.SliceOf(reflect.TypeOf(model).Elem()))
@@ -126,15 +182,32 @@ func resolveResourceRelations(input map[string]any, allowedFields []string, rela
if !ok || strings.TrimSpace(identity) == "" {
return nil, errors.New("invalid relation identity")
}
var related struct{ ID uint64 }
if err := impl.DBService.Model(relation.Model).Select("id").Where("identity = ?", identity).First(&related).Error; err != nil {
id, err := resolveIdentityID(relation.Model, identity, true)
if err != nil {
return nil, err
}
values[relation.Column] = related.ID
values[relation.Column] = id
}
return values, nil
}
// resolveIdentityID is the only boundary that converts a public identity to a
// persistence-only numeric key. Callers must never bind a client supplied ID.
func resolveIdentityID(model any, identity string, required bool) (uint64, error) {
identity = strings.TrimSpace(identity)
if identity == "" {
if required {
return 0, errors.New("missing required relation")
}
return 0, nil
}
var related struct{ ID uint64 }
if err := impl.DBService.Model(model).Select("id").Where("identity = ?", identity).First(&related).Error; err != nil {
return 0, err
}
return related.ID, nil
}
func relationColumns(relations []ResourceRelation) []string {
columns := make([]string, 0, len(relations))
for _, relation := range relations {
@@ -266,3 +339,75 @@ func GetDeliveryTrack(ctx *gin.Context) {
}
infra.Response.Success(ctx, resourceResponse(gin.H{"track": track, "points": points}))
}
type ecCategoryView struct {
Identity string `json:"identity"`
ParentIdentity string `json:"parent_identity,omitempty"`
Name string `json:"name"`
SortNo int `json:"sort_no"`
Status string `json:"status"`
}
func ecCategoryViews(list []models.EcCategory) ([]ecCategoryView, error) {
parents := make(map[uint64]string)
for _, item := range list {
if item.ParentID != 0 {
parents[item.ParentID] = ""
}
}
if len(parents) > 0 {
var rows []struct {
ID uint64
Identity string
}
ids := make([]uint64, 0, len(parents))
for id := range parents {
ids = append(ids, id)
}
if err := impl.DBService.Model(&models.EcCategory{}).Select("id", "identity").Where("id IN ?", ids).Find(&rows).Error; err != nil {
return nil, err
}
for _, row := range rows {
parents[row.ID] = row.Identity
}
}
views := make([]ecCategoryView, 0, len(list))
for _, item := range list {
views = append(views, ecCategoryView{Identity: item.Identity, ParentIdentity: parents[item.ParentID], Name: item.Name, SortNo: item.SortNo, Status: item.Status})
}
return views, nil
}
func ListEcCategory(ctx *gin.Context) {
page, size := pageSize(ctx)
var list []models.EcCategory
var total int64
if err := impl.DBService.Model(&models.EcCategory{}).Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
if err := impl.DBService.Order("sort_no asc, id asc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
views, err := ecCategoryViews(list)
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"total": total, "list": views})
}
func GetEcCategory(ctx *gin.Context) {
var category models.EcCategory
if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&category).Error; err != nil {
respondRecordError(ctx, err)
return
}
views, err := ecCategoryViews([]models.EcCategory{category})
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, views[0])
}