diff --git a/backend/api/internal/logic/platform/delivery.go b/backend/api/internal/logic/platform/delivery.go new file mode 100644 index 0000000..83a5236 --- /dev/null +++ b/backend/api/internal/logic/platform/delivery.go @@ -0,0 +1,45 @@ +package platform + +import ( + "git.apinb.com/bsm-sdk/core/errcode" + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" + "github.com/gin-gonic/gin" +) + +// ListDeliveryBasic 查询配送点分页列表。 +func ListDeliveryBasic(ctx *gin.Context) { listPage[models.DeliveryBasic](ctx) } + +// GetDeliveryBasic 查询一个配送点。 +func GetDeliveryBasic(ctx *gin.Context) { getByIdentity[models.DeliveryBasic](ctx) } + +// CreateDeliveryBasic 创建配送点档案。 +func CreateDeliveryBasic(ctx *gin.Context) { + var request models.DeliveryBasic + if err := ctx.ShouldBindJSON(&request); err != nil || request.DeliveryCode == "" || request.Name == "" { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + request.Entity = newEntity("draft") + if err := impl.DBService.Create(&request).Error; err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, request) +} + +// UpdateDeliveryBasic 更新配送点基础资料。 +func UpdateDeliveryBasic(ctx *gin.Context) { + var request struct { + GasBasicID uint64 `json:"gas_basic_id"` + Name string `json:"name" binding:"required,max=128"` + Principal string `json:"principal" binding:"max=64"` + Address string `json:"address" binding:"max=255"` + } + if err := ctx.ShouldBindJSON(&request); err != nil { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + updateByIdentity(ctx, &models.DeliveryBasic{}, gin.H{"gas_basic_id": request.GasBasicID, "name": request.Name, "principal": request.Principal, "address": request.Address}) +} diff --git a/backend/api/internal/logic/platform/gas.go b/backend/api/internal/logic/platform/gas.go new file mode 100644 index 0000000..21e9792 --- /dev/null +++ b/backend/api/internal/logic/platform/gas.go @@ -0,0 +1,47 @@ +package platform + +import ( + "git.apinb.com/bsm-sdk/core/errcode" + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" + "github.com/gin-gonic/gin" +) + +// ListGasBasic 查询可燃气体站分页列表。 +func ListGasBasic(ctx *gin.Context) { listPage[models.GasBasic](ctx) } + +// GetGasBasic 查询一个可燃气体站。 +func GetGasBasic(ctx *gin.Context) { getByIdentity[models.GasBasic](ctx) } + +// CreateGasBasic 创建可燃气体站档案。 +func CreateGasBasic(ctx *gin.Context) { + var request models.GasBasic + if err := ctx.ShouldBindJSON(&request); err != nil || request.Code == "" || request.Name == "" { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + request.Entity = newEntity("draft") + if err := impl.DBService.Create(&request).Error; err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, request) +} + +// UpdateGasBasic 更新可燃气体站基础资料。 +func UpdateGasBasic(ctx *gin.Context) { + var request struct { + Name string `json:"name" binding:"required,max=128"` + CreditCode string `json:"credit_code" binding:"max=64"` + Principal string `json:"principal" binding:"max=64"` + Address string `json:"address" binding:"max=255"` + Longitude string `json:"longitude" binding:"max=32"` + Latitude string `json:"latitude" binding:"max=32"` + } + if err := ctx.ShouldBindJSON(&request); err != nil { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + updateByIdentity(ctx, &models.GasBasic{}, gin.H{"name": request.Name, "credit_code": request.CreditCode, "principal": request.Principal, "address": request.Address, "longitude": request.Longitude, "latitude": request.Latitude}) +} diff --git a/backend/api/internal/logic/platform/health.go b/backend/api/internal/logic/platform/health.go new file mode 100644 index 0000000..ddb4ee2 --- /dev/null +++ b/backend/api/internal/logic/platform/health.go @@ -0,0 +1,22 @@ +package platform + +import ( + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" + "github.com/gin-gonic/gin" +) + +// PingHello 返回匿名健康状态。 +func PingHello(ctx *gin.Context) { + infra.Response.Success(ctx, gin.H{"service": "platform-api", "status": "ok"}) +} + +// DashboardOverview 返回平台总后台的运营概览数据。 +func DashboardOverview(ctx *gin.Context) { + overview, err := models.GetDashboardOverview() + if err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, overview) +} diff --git a/backend/api/internal/logic/platform/platform.go b/backend/api/internal/logic/platform/platform.go index c2f4977..fe2be58 100644 --- a/backend/api/internal/logic/platform/platform.go +++ b/backend/api/internal/logic/platform/platform.go @@ -1,4 +1,4 @@ -// Package platform 汇聚平台总后台的同步 HTTP 业务逻辑。 +// Package platform 提供平台总后台的同步 HTTP 业务逻辑。 package platform import ( @@ -13,277 +13,6 @@ import ( "gorm.io/gorm" ) -// PingHello 返回匿名健康状态。 -func PingHello(ctx *gin.Context) { - infra.Response.Success(ctx, gin.H{"service": "platform-api", "status": "ok"}) -} - -// DashboardOverview 返回平台总后台的运营概览数据。 -func DashboardOverview(ctx *gin.Context) { - overview, err := models.GetDashboardOverview() - if err != nil { - infra.Response.Error(ctx, err) - return - } - infra.Response.Success(ctx, overview) -} - -// ListGasBasic 查询可燃气体站分页列表。 -func ListGasBasic(ctx *gin.Context) { listPage[models.GasBasic](ctx) } - -// GetGasBasic 查询一个可燃气体站。 -func GetGasBasic(ctx *gin.Context) { getByIdentity[models.GasBasic](ctx) } - -// CreateGasBasic 创建可燃气体站档案。 -func CreateGasBasic(ctx *gin.Context) { - var request models.GasBasic - if err := ctx.ShouldBindJSON(&request); err != nil || request.Code == "" || request.Name == "" { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - request.Entity = newEntity("draft") - if err := impl.DBService.Create(&request).Error; err != nil { - infra.Response.Error(ctx, err) - return - } - infra.Response.Success(ctx, request) -} - -// UpdateGasBasic 更新可燃气体站基础资料。 -func UpdateGasBasic(ctx *gin.Context) { - var request struct { - Name string `json:"name" binding:"required,max=128"` - CreditCode string `json:"credit_code" binding:"max=64"` - Principal string `json:"principal" binding:"max=64"` - Address string `json:"address" binding:"max=255"` - Longitude string `json:"longitude" binding:"max=32"` - Latitude string `json:"latitude" binding:"max=32"` - } - if err := ctx.ShouldBindJSON(&request); err != nil { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - updateByIdentity(ctx, &models.GasBasic{}, gin.H{"name": request.Name, "credit_code": request.CreditCode, "principal": request.Principal, "address": request.Address, "longitude": request.Longitude, "latitude": request.Latitude}) -} - -// ListDeliveryBasic 查询配送点分页列表。 -func ListDeliveryBasic(ctx *gin.Context) { listPage[models.DeliveryBasic](ctx) } - -// GetDeliveryBasic 查询一个配送点。 -func GetDeliveryBasic(ctx *gin.Context) { getByIdentity[models.DeliveryBasic](ctx) } - -// CreateDeliveryBasic 创建配送点档案。 -func CreateDeliveryBasic(ctx *gin.Context) { - var request models.DeliveryBasic - if err := ctx.ShouldBindJSON(&request); err != nil || request.DeliveryCode == "" || request.Name == "" { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - request.Entity = newEntity("draft") - if err := impl.DBService.Create(&request).Error; err != nil { - infra.Response.Error(ctx, err) - return - } - infra.Response.Success(ctx, request) -} - -// UpdateDeliveryBasic 更新配送点基础资料。 -func UpdateDeliveryBasic(ctx *gin.Context) { - var request struct { - GasBasicID uint64 `json:"gas_basic_id"` - Name string `json:"name" binding:"required,max=128"` - Principal string `json:"principal" binding:"max=64"` - Address string `json:"address" binding:"max=255"` - } - if err := ctx.ShouldBindJSON(&request); err != nil { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - updateByIdentity(ctx, &models.DeliveryBasic{}, gin.H{"gas_basic_id": request.GasBasicID, "name": request.Name, "principal": request.Principal, "address": request.Address}) -} - -// ListStaff 查询服务人员分页列表。 -func ListStaff(ctx *gin.Context) { listPage[models.StaffAccount](ctx) } - -// GetStaff 查询一个服务人员档案。 -func GetStaff(ctx *gin.Context) { getByIdentity[models.StaffAccount](ctx) } - -// CreateStaff 创建服务人员档案。 -func CreateStaff(ctx *gin.Context) { - var request models.StaffAccount - if err := ctx.ShouldBindJSON(&request); err != nil || request.Name == "" { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - request.Entity = newEntity("draft") - if request.WorkStatus == "" { - request.WorkStatus = "off_duty" - } - if err := impl.DBService.Create(&request).Error; err != nil { - infra.Response.Error(ctx, err) - return - } - infra.Response.Success(ctx, request) -} - -// UpdateStaff 更新服务人员档案。 -func UpdateStaff(ctx *gin.Context) { - var request struct { - Name string `json:"name" binding:"required,max=64"` - Phone string `json:"phone" binding:"max=32"` - Avatar string `json:"avatar" binding:"max=512"` - RoleCode string `json:"role_code" binding:"max=64"` - GasBasicID uint64 `json:"gas_basic_id"` - DeliveryBasicID uint64 `json:"delivery_basic_id"` - WorkStatus string `json:"work_status" binding:"max=32"` - } - if err := ctx.ShouldBindJSON(&request); err != nil { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - updateByIdentity(ctx, &models.StaffAccount{}, gin.H{"name": request.Name, "phone": request.Phone, "avatar": request.Avatar, "role_code": request.RoleCode, "gas_basic_id": request.GasBasicID, "delivery_basic_id": request.DeliveryBasicID, "work_status": request.WorkStatus}) -} - -// ListUser 查询业主客户分页列表。 -func ListUser(ctx *gin.Context) { listPage[models.UserAccount](ctx) } - -// GetUser 查询一个业主客户档案。 -func GetUser(ctx *gin.Context) { getByIdentity[models.UserAccount](ctx) } - -// CreateUser 创建业主客户档案。 -func CreateUser(ctx *gin.Context) { - var request models.UserAccount - if err := ctx.ShouldBindJSON(&request); err != nil || request.Name == "" { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - request.Entity = newEntity("enabled") - if err := impl.DBService.Create(&request).Error; err != nil { - infra.Response.Error(ctx, err) - return - } - infra.Response.Success(ctx, request) -} - -// UpdateUser 更新业主客户档案。 -func UpdateUser(ctx *gin.Context) { - var request struct { - Name string `json:"name" binding:"required,max=64"` - Phone string `json:"phone" binding:"max=32"` - Avatar string `json:"avatar" binding:"max=512"` - RealName string `json:"real_name" binding:"max=64"` - } - if err := ctx.ShouldBindJSON(&request); err != nil { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - updateByIdentity(ctx, &models.UserAccount{}, gin.H{"name": request.Name, "phone": request.Phone, "avatar": request.Avatar, "real_name": request.RealName}) -} - -// ListPlatformRole 查询平台角色分页列表。 -func ListPlatformRole(ctx *gin.Context) { listPage[models.PlatformRole](ctx) } - -// GetPlatformRole 查询一个平台角色。 -func GetPlatformRole(ctx *gin.Context) { getByIdentity[models.PlatformRole](ctx) } - -// CreatePlatformRole 创建非内置平台角色。 -func CreatePlatformRole(ctx *gin.Context) { - var request models.PlatformRole - if err := ctx.ShouldBindJSON(&request); err != nil || request.RoleCode == "" || request.Name == "" || request.RoleCode == "root" { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - request.Entity = newEntity("enabled") - request.IsSystem = false - if request.DataScope == "" { - request.DataScope = "global" - } - if err := impl.DBService.Create(&request).Error; err != nil { - infra.Response.Error(ctx, err) - return - } - infra.Response.Success(ctx, request) -} - -// UpdatePlatformRole 更新非内置平台角色。 -func UpdatePlatformRole(ctx *gin.Context) { - var request struct { - Name string `json:"name" binding:"required,max=64"` - DataScope string `json:"data_scope" binding:"required,max=32"` - } - if err := ctx.ShouldBindJSON(&request); err != nil { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - var role models.PlatformRole - if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil { - respondRecordError(ctx, err) - return - } - if role.IsSystem { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - if err := impl.DBService.Model(&role).Updates(gin.H{"name": request.Name, "data_scope": request.DataScope}).Error; err != nil { - infra.Response.Error(ctx, err) - return - } - infra.Response.Success(ctx, role) -} - -// UpdatePlatformRoleStatus 更新非内置平台角色状态,root 等系统角色始终受保护。 -func UpdatePlatformRoleStatus(ctx *gin.Context) { - var request struct { - Status string `json:"status" binding:"required,max=32"` - } - if err := ctx.ShouldBindJSON(&request); err != nil { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - var role models.PlatformRole - if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil { - respondRecordError(ctx, err) - return - } - if role.IsSystem { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - if err := impl.DBService.Model(&role).Update("status", request.Status).Error; err != nil { - infra.Response.Error(ctx, err) - return - } - infra.Response.Success(ctx, gin.H{"updated": true}) -} - -// ArchivePlatformRole 归档非内置平台角色,root 等系统角色始终受保护。 -func ArchivePlatformRole(ctx *gin.Context) { - var role models.PlatformRole - if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil { - respondRecordError(ctx, err) - return - } - if role.IsSystem { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - if err := impl.DBService.Model(&role).Update("status", "archived").Error; err != nil { - infra.Response.Error(ctx, err) - return - } - infra.Response.Success(ctx, gin.H{"updated": true}) -} - -// ListPlatformMenu 返回菜单树构建所需的有序菜单列表。 -func ListPlatformMenu(ctx *gin.Context) { - var list []models.PlatformMenu - if err := impl.DBService.Order("sort_no asc, id asc").Find(&list).Error; err != nil { - infra.Response.Error(ctx, err) - return - } - infra.Response.Success(ctx, gin.H{"total": len(list), "list": list}) -} - // UpdateRecordStatus 更新主表状态,停用和归档均保留历史记录。 func UpdateRecordStatus(ctx *gin.Context, model any) { var request struct { @@ -293,27 +22,12 @@ func UpdateRecordStatus(ctx *gin.Context, model any) { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } - updateByIdentity(ctx, model, gin.H{"status": request.Status}) + updateAllowedByIdentity(ctx, model, gin.H{"status": request.Status}, []string{"status"}) } // ArchiveRecord 通过 archived 状态实现逻辑删除,不物理删除主数据。 func ArchiveRecord(ctx *gin.Context, model any) { - updateByIdentity(ctx, model, gin.H{"status": "archived"}) -} - -// ListPlatfromAccount 查询平台账号列表,手机号在展示层脱敏。 -func ListPlatfromAccount(ctx *gin.Context) { - page, size := pageSize(ctx) - list, total, err := models.ListPlatfromAccount(page, size) - if err != nil { - infra.Response.Error(ctx, err) - return - } - views := make([]gin.H, 0, len(list)) - for _, item := range list { - views = append(views, gin.H{"identity": item.Identity, "username": item.Username, "display_name": item.DisplayName, "avatar": item.Avatar, "phone_masked": maskPhone(item.Phone), "platform_role_code": item.PlatformRoleCode, "status": item.Status}) - } - infra.Response.Success(ctx, gin.H{"total": total, "list": views}) + updateAllowedByIdentity(ctx, model, archiveValues(), []string{"status"}) } func newEntity(status string) models.Entity { @@ -345,6 +59,25 @@ func getByIdentity[T any](ctx *gin.Context) { infra.Response.Success(ctx, data) } +func updateAllowedByIdentity(ctx *gin.Context, model any, values map[string]any, allowedFields []string) { + values = filterFields(values, allowedFields) + if len(values) == 0 { + infra.Response.Success(ctx, gin.H{"updated": false}) + return + } + + result := impl.DBService.Model(model).Where("identity = ?", ctx.Param("identity")).Updates(values) + if result.Error != nil { + infra.Response.Error(ctx, result.Error) + return + } + if result.RowsAffected == 0 { + infra.Response.Error(ctx, errcode.ErrRecordNotFound) + return + } + infra.Response.Success(ctx, gin.H{"updated": true}) +} + func updateByIdentity(ctx *gin.Context, model any, values map[string]any) { result := impl.DBService.Model(model).Where("identity = ?", ctx.Param("identity")).Updates(values) if result.Error != nil { diff --git a/backend/api/internal/logic/platform/resource.go b/backend/api/internal/logic/platform/resource.go index c81e3d6..ebe1489 100644 --- a/backend/api/internal/logic/platform/resource.go +++ b/backend/api/internal/logic/platform/resource.go @@ -1,5 +1,11 @@ package platform +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + // ResourceMode describes the operations a platform-admin resource supports. type ResourceMode string @@ -17,6 +23,49 @@ type ResourceContract struct { Mode ResourceMode } +// ResourceDefinition describes a route resource and the fields it may change. +type ResourceDefinition struct { + Domain string + Name string + Model func() any + Mode ResourceMode + CreateFields []string + UpdateFields []string +} + +// Allows reports whether a resource mode supports an HTTP method. +func (d ResourceDefinition) Allows(method string) bool { + switch d.Mode { + case ReadOnly: + return method == http.MethodGet + case AppendOnly: + return method == http.MethodGet || method == http.MethodPost + case Writable: + return method == http.MethodGet || method == http.MethodPost || method == http.MethodPut || method == http.MethodPatch || method == http.MethodDelete + default: + return false + } +} + +func archiveValues() gin.H { + return gin.H{"status": "archived"} +} + +func filterFields(values map[string]any, allowedFields []string) gin.H { + allowed := make(map[string]struct{}, len(allowedFields)) + for _, field := range allowedFields { + allowed[field] = struct{}{} + } + + filtered := make(gin.H, len(allowed)) + for field, value := range values { + if _, ok := allowed[field]; ok { + filtered[field] = value + } + } + return filtered +} + // ExpectedResources returns the complete platform-admin resource catalogue. func ExpectedResources() []ResourceContract { return []ResourceContract{ diff --git a/backend/api/internal/logic/platform/resource_test.go b/backend/api/internal/logic/platform/resource_test.go index 8241f79..9016801 100644 --- a/backend/api/internal/logic/platform/resource_test.go +++ b/backend/api/internal/logic/platform/resource_test.go @@ -1,6 +1,9 @@ package platform -import "testing" +import ( + "net/http" + "testing" +) func TestExpectedResources(t *testing.T) { assertContract(t, ExpectedResources(), "gas", "gas_basic", Writable, "list") @@ -8,6 +11,32 @@ func TestExpectedResources(t *testing.T) { assertContract(t, ExpectedResources(), "wallet", "wallet_ledger", ReadOnly, "list") } +func TestResourceDefinitionAllowsOnlySupportedMethods(t *testing.T) { + if (ResourceDefinition{Mode: ReadOnly}).Allows(http.MethodPost) { + t.Fatal("readonly allows POST") + } + if !(ResourceDefinition{Mode: AppendOnly}).Allows(http.MethodPost) { + t.Fatal("append-only rejects POST") + } + if (ResourceDefinition{Mode: AppendOnly}).Allows(http.MethodDelete) { + t.Fatal("append-only allows DELETE") + } +} + +func TestArchiveValuesOnlyArchives(t *testing.T) { + got := archiveValues() + if len(got) != 1 || got["status"] != "archived" { + t.Fatalf("unexpected archive values: %#v", got) + } +} + +func TestFilterFieldsKeepsOnlyAllowedKeys(t *testing.T) { + got := filterFields(map[string]any{"name": "n", "password_hash": "x"}, []string{"name"}) + if len(got) != 1 || got["name"] != "n" { + t.Fatalf("unexpected filtered fields: %#v", got) + } +} + func assertContract(t *testing.T, contracts []ResourceContract, domain, name string, mode ResourceMode, pageKind string) { t.Helper() for _, contract := range contracts { diff --git a/backend/api/internal/logic/platform/role.go b/backend/api/internal/logic/platform/role.go new file mode 100644 index 0000000..4936281 --- /dev/null +++ b/backend/api/internal/logic/platform/role.go @@ -0,0 +1,128 @@ +package platform + +import ( + "git.apinb.com/bsm-sdk/core/errcode" + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" + "github.com/gin-gonic/gin" +) + +// ListPlatformRole 查询平台角色分页列表。 +func ListPlatformRole(ctx *gin.Context) { listPage[models.PlatformRole](ctx) } + +// GetPlatformRole 查询一个平台角色。 +func GetPlatformRole(ctx *gin.Context) { getByIdentity[models.PlatformRole](ctx) } + +// CreatePlatformRole 创建非内置平台角色。 +func CreatePlatformRole(ctx *gin.Context) { + var request models.PlatformRole + if err := ctx.ShouldBindJSON(&request); err != nil || request.RoleCode == "" || request.Name == "" || request.RoleCode == "root" { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + request.Entity = newEntity("enabled") + request.IsSystem = false + if request.DataScope == "" { + request.DataScope = "global" + } + if err := impl.DBService.Create(&request).Error; err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, request) +} + +// UpdatePlatformRole 更新非内置平台角色。 +func UpdatePlatformRole(ctx *gin.Context) { + var request struct { + Name string `json:"name" binding:"required,max=64"` + DataScope string `json:"data_scope" binding:"required,max=32"` + } + if err := ctx.ShouldBindJSON(&request); err != nil { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + var role models.PlatformRole + if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil { + respondRecordError(ctx, err) + return + } + if role.IsSystem { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + if err := impl.DBService.Model(&role).Updates(gin.H{"name": request.Name, "data_scope": request.DataScope}).Error; err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, role) +} + +// UpdatePlatformRoleStatus 更新非内置平台角色状态,系统角色始终受保护。 +func UpdatePlatformRoleStatus(ctx *gin.Context) { + var request struct { + Status string `json:"status" binding:"required,max=32"` + } + if err := ctx.ShouldBindJSON(&request); err != nil { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + var role models.PlatformRole + if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil { + respondRecordError(ctx, err) + return + } + if role.IsSystem { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + if err := impl.DBService.Model(&role).Update("status", request.Status).Error; err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, gin.H{"updated": true}) +} + +// ArchivePlatformRole 归档非内置平台角色,系统角色始终受保护。 +func ArchivePlatformRole(ctx *gin.Context) { + var role models.PlatformRole + if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil { + respondRecordError(ctx, err) + return + } + if role.IsSystem { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + if err := impl.DBService.Model(&role).Update("status", "archived").Error; err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, gin.H{"updated": true}) +} + +// ListPlatformMenu 返回菜单树构建所需的有序菜单列表。 +func ListPlatformMenu(ctx *gin.Context) { + var list []models.PlatformMenu + if err := impl.DBService.Order("sort_no asc, id asc").Find(&list).Error; err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, gin.H{"total": len(list), "list": list}) +} + +// ListPlatfromAccount 查询平台账号列表,手机号在展示层脱敏。 +func ListPlatfromAccount(ctx *gin.Context) { + page, size := pageSize(ctx) + list, total, err := models.ListPlatfromAccount(page, size) + if err != nil { + infra.Response.Error(ctx, err) + return + } + views := make([]gin.H, 0, len(list)) + for _, item := range list { + views = append(views, gin.H{"identity": item.Identity, "username": item.Username, "display_name": item.DisplayName, "avatar": item.Avatar, "phone_masked": maskPhone(item.Phone), "platform_role_code": item.PlatformRoleCode, "status": item.Status}) + } + infra.Response.Success(ctx, gin.H{"total": total, "list": views}) +} diff --git a/backend/api/internal/logic/platform/staff.go b/backend/api/internal/logic/platform/staff.go new file mode 100644 index 0000000..317f1c3 --- /dev/null +++ b/backend/api/internal/logic/platform/staff.go @@ -0,0 +1,51 @@ +package platform + +import ( + "git.apinb.com/bsm-sdk/core/errcode" + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" + "github.com/gin-gonic/gin" +) + +// ListStaff 查询服务人员分页列表。 +func ListStaff(ctx *gin.Context) { listPage[models.StaffAccount](ctx) } + +// GetStaff 查询一个服务人员档案。 +func GetStaff(ctx *gin.Context) { getByIdentity[models.StaffAccount](ctx) } + +// CreateStaff 创建服务人员档案。 +func CreateStaff(ctx *gin.Context) { + var request models.StaffAccount + if err := ctx.ShouldBindJSON(&request); err != nil || request.Name == "" { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + request.Entity = newEntity("draft") + if request.WorkStatus == "" { + request.WorkStatus = "off_duty" + } + if err := impl.DBService.Create(&request).Error; err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, request) +} + +// UpdateStaff 更新服务人员档案。 +func UpdateStaff(ctx *gin.Context) { + var request struct { + Name string `json:"name" binding:"required,max=64"` + Phone string `json:"phone" binding:"max=32"` + Avatar string `json:"avatar" binding:"max=512"` + RoleCode string `json:"role_code" binding:"max=64"` + GasBasicID uint64 `json:"gas_basic_id"` + DeliveryBasicID uint64 `json:"delivery_basic_id"` + WorkStatus string `json:"work_status" binding:"max=32"` + } + if err := ctx.ShouldBindJSON(&request); err != nil { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + updateByIdentity(ctx, &models.StaffAccount{}, gin.H{"name": request.Name, "phone": request.Phone, "avatar": request.Avatar, "role_code": request.RoleCode, "gas_basic_id": request.GasBasicID, "delivery_basic_id": request.DeliveryBasicID, "work_status": request.WorkStatus}) +} diff --git a/backend/api/internal/logic/platform/user.go b/backend/api/internal/logic/platform/user.go new file mode 100644 index 0000000..63fee53 --- /dev/null +++ b/backend/api/internal/logic/platform/user.go @@ -0,0 +1,45 @@ +package platform + +import ( + "git.apinb.com/bsm-sdk/core/errcode" + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" + "github.com/gin-gonic/gin" +) + +// ListUser 查询业主客户分页列表。 +func ListUser(ctx *gin.Context) { listPage[models.UserAccount](ctx) } + +// GetUser 查询一个业主客户档案。 +func GetUser(ctx *gin.Context) { getByIdentity[models.UserAccount](ctx) } + +// CreateUser 创建业主客户档案。 +func CreateUser(ctx *gin.Context) { + var request models.UserAccount + if err := ctx.ShouldBindJSON(&request); err != nil || request.Name == "" { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + request.Entity = newEntity("enabled") + if err := impl.DBService.Create(&request).Error; err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, request) +} + +// UpdateUser 更新业主客户档案。 +func UpdateUser(ctx *gin.Context) { + var request struct { + Name string `json:"name" binding:"required,max=64"` + Phone string `json:"phone" binding:"max=32"` + Avatar string `json:"avatar" binding:"max=512"` + RealName string `json:"real_name" binding:"max=64"` + } + if err := ctx.ShouldBindJSON(&request); err != nil { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + updateByIdentity(ctx, &models.UserAccount{}, gin.H{"name": request.Name, "phone": request.Phone, "avatar": request.Avatar, "real_name": request.RealName}) +}