完善配送端资源搜索与资质导航
This commit is contained in:
@@ -1,33 +1,44 @@
|
||||
package delivery
|
||||
|
||||
import "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
|
||||
|
||||
type ResourceContract struct {
|
||||
Domain string `json:"domain"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
PageKind string `json:"pageKind"`
|
||||
Mode string `json:"mode"`
|
||||
Domain string `json:"domain"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
PageKind string `json:"pageKind"`
|
||||
Mode string `json:"mode"`
|
||||
SearchFields []common.KeywordSearchField `json:"searchFields,omitempty"`
|
||||
}
|
||||
|
||||
func ExpectedResources() []ResourceContract {
|
||||
items := []ResourceContract{
|
||||
{"profile", "delivery_profile", "/delivery_profile", "list", "readonly"},
|
||||
{"staff", "staff_account", "/staff_account", "list", "writable"},
|
||||
{"staff", "staff_credential", "/staff_credential", "list", "writable"},
|
||||
{"user", "user_account", "/user_account", "list", "writable"},
|
||||
{"user", "user_address", "/user_address", "list", "writable"},
|
||||
{"contract", "gasorder_contract", "/gasorder_contract", "list", "managed"},
|
||||
{"contract", "gasorder_contract_product", "/gasorder_contract_product", "list", "append_only"},
|
||||
{"contract", "gasorder_contract_revision", "/gasorder_contract_revision", "list", "readonly"},
|
||||
{"contract", "product_info", "/product_info", "list", "readonly"},
|
||||
{"gasorder", "gasorder_basic", "/gasorder_basic", "list", "append_only"},
|
||||
{"finance", "wallet_basic", "/wallet_basic", "list", "readonly"},
|
||||
{"finance", "wallet_bank", "/wallet_bank", "list", "readonly"},
|
||||
{"finance", "payment_order", "/payment_order", "list", "readonly"},
|
||||
{"finance", "wallet_record", "/wallet_record", "list", "readonly"},
|
||||
{"finance", "payment_refund", "/payment_refund", "list", "readonly"},
|
||||
{"finance", "wallet_recharge", "/wallet_recharge", "list", "append_only"},
|
||||
{"finance", "wallet_apply_cash", "/wallet_apply_cash", "list", "append_only"},
|
||||
{"finance", "fin_settlement", "/fin_settlement", "list", "readonly"},
|
||||
resourceContract("profile", "delivery_profile", "readonly"),
|
||||
resourceContract("staff", "staff_account", "writable"),
|
||||
resourceContract("staff", "staff_credential", "writable"),
|
||||
resourceContract("user", "user_account", "writable"),
|
||||
resourceContract("user", "user_address", "writable"),
|
||||
resourceContract("contract", "gasorder_contract", "managed"),
|
||||
resourceContract("contract", "gasorder_contract_product", "append_only"),
|
||||
resourceContract("contract", "gasorder_contract_revision", "readonly"),
|
||||
resourceContract("contract", "product_info", "readonly"),
|
||||
resourceContract("gasorder", "gasorder_basic", "append_only"),
|
||||
resourceContract("finance", "wallet_basic", "readonly"),
|
||||
resourceContract("finance", "wallet_bank", "readonly"),
|
||||
resourceContract("finance", "payment_order", "readonly"),
|
||||
resourceContract("finance", "wallet_record", "readonly"),
|
||||
resourceContract("finance", "payment_refund", "readonly"),
|
||||
resourceContract("finance", "wallet_recharge", "append_only"),
|
||||
resourceContract("finance", "wallet_apply_cash", "append_only"),
|
||||
resourceContract("finance", "fin_settlement", "readonly"),
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// resourceContract 创建配送端标准资源契约,并附加显式搜索字段。
|
||||
func resourceContract(domain, name, mode string) ResourceContract {
|
||||
return ResourceContract{
|
||||
Domain: domain, Name: name, Path: "/" + name, PageKind: "list", Mode: mode,
|
||||
SearchFields: resourceSearchFields(name),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// 功能描述:验证配送端资源搜索契约与页面能力保持一致。版本:v1.0.0。
|
||||
package delivery
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestExpectedResourcesExposeSearchContract 验证有效搜索与隐藏搜索的资源边界。
|
||||
func TestExpectedResourcesExposeSearchContract(t *testing.T) {
|
||||
resources := ExpectedResources()
|
||||
byName := make(map[string]ResourceContract, len(resources))
|
||||
for _, resource := range resources {
|
||||
byName[resource.Name] = resource
|
||||
}
|
||||
credential := byName["staff_credential"].SearchFields
|
||||
if len(credential) != 2 || credential[0].Key != "credential_type" || credential[1].Key != "credential_no" {
|
||||
t.Fatalf("人员资质搜索契约不完整:%#v", credential)
|
||||
}
|
||||
for _, name := range []string{"delivery_profile", "user_address", "wallet_bank"} {
|
||||
if len(byName[name].SearchFields) != 0 {
|
||||
t.Fatalf("%s 不应显示无效搜索:%#v", name, byName[name].SearchFields)
|
||||
}
|
||||
}
|
||||
channel := byName["payment_order"].SearchFields[2]
|
||||
if channel.Kind != "enum" || len(channel.Values) != 3 {
|
||||
t.Fatalf("支付渠道中英文搜索契约不完整:%#v", channel)
|
||||
}
|
||||
}
|
||||
62
backend/api/internal/logic/delivery/resource_search.go
Normal file
62
backend/api/internal/logic/delivery/resource_search.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Package delivery 定义配送点管理端公开的资源搜索契约。
|
||||
// 版本:v1.0.0
|
||||
package delivery
|
||||
|
||||
import (
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||
)
|
||||
|
||||
// deliveryResourceSearchFields 只公开页面能够解释且服务端确实执行的搜索字段。
|
||||
var deliveryResourceSearchFields = map[string][]common.KeywordSearchField{
|
||||
"staff_account": {searchText("username")},
|
||||
"staff_credential": {searchText("credential_type"), searchText("credential_no")},
|
||||
"user_account": {searchText("username")},
|
||||
"gasorder_contract": {searchText("contract_no"), searchText("title")},
|
||||
"gasorder_contract_product": {searchText("product_code"), searchText("product_type_name")},
|
||||
"gasorder_contract_revision": {searchEnum("action", searchOption("activate", "启用"), searchOption("renew", "续签"), searchOption("terminate", "终止"))},
|
||||
"product_info": {searchText("code"), searchText("name")},
|
||||
"gasorder_basic": {searchText("request_no"), searchEnum("creator_type",
|
||||
searchOption("user", "用户"), searchOption("staff", "工作人员"),
|
||||
searchOption("delivery", "配送点"), searchOption("gas", "气站"))},
|
||||
"payment_order": {searchText("payment_no"), searchText("request_no"), searchEnum("channel",
|
||||
searchOption("wechat", "微信"), searchOption("alipay", "支付宝"), searchOption("mock", "模拟支付"))},
|
||||
"wallet_record": {searchText("record_no"), searchText("request_no")},
|
||||
"payment_refund": {searchText("refund_no"), searchText("request_no")},
|
||||
"wallet_recharge": {searchText("record_no"), searchText("request_no")},
|
||||
"wallet_apply_cash": {searchText("cash_no"), searchText("request_no"), searchEnum("channel",
|
||||
searchOption("bank", "银行卡"), searchOption("alipay", "支付宝"), searchOption("wechat", "微信"))},
|
||||
"fin_settlement": {searchText("settlement_no")},
|
||||
}
|
||||
|
||||
// init 只注册平台总后台尚未注册的模型;共享模型继续复用全局策略。
|
||||
func init() {
|
||||
common.RegisterKeywordSearchPolicy(&models.GasorderContractProduct{}, deliveryResourceSearchFields["gasorder_contract_product"])
|
||||
common.RegisterKeywordSearchPolicy(&models.GasorderContractRevision{}, deliveryResourceSearchFields["gasorder_contract_revision"])
|
||||
common.RegisterKeywordSearchPolicy(&models.PaymentOrder{}, deliveryResourceSearchFields["payment_order"])
|
||||
common.RegisterKeywordSearchPolicy(&models.WalletRecord{}, deliveryResourceSearchFields["wallet_record"])
|
||||
}
|
||||
|
||||
// resourceSearchFields 返回搜索契约副本,防止调用方修改全局定义。
|
||||
func resourceSearchFields(name string) []common.KeywordSearchField {
|
||||
fields := deliveryResourceSearchFields[name]
|
||||
result := make([]common.KeywordSearchField, 0, len(fields))
|
||||
for _, field := range fields {
|
||||
copied := field
|
||||
copied.Values = append([]common.KeywordSearchValue(nil), field.Values...)
|
||||
result = append(result, copied)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func searchText(key string) common.KeywordSearchField {
|
||||
return common.KeywordSearchField{Key: key, Kind: common.KeywordSearchText}
|
||||
}
|
||||
|
||||
func searchEnum(key string, values ...common.KeywordSearchValue) common.KeywordSearchField {
|
||||
return common.KeywordSearchField{Key: key, Kind: common.KeywordSearchEnum, Values: values}
|
||||
}
|
||||
|
||||
func searchOption(value, label string) common.KeywordSearchValue {
|
||||
return common.KeywordSearchValue{Value: value, Label: label}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package delivery
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.apinb.com/bsm-sdk/core/errcode"
|
||||
@@ -187,17 +188,38 @@ func ArchiveStaff(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
func credentialQuery(point models.DeliveryBasic) *gorm.DB {
|
||||
return common.ActiveRecords(db().Model(&models.StaffCredential{})).
|
||||
return credentialQueryWithDB(db(), point)
|
||||
}
|
||||
|
||||
// credentialQueryWithDB 固定资质所属配送点、气站和配送角色范围。
|
||||
func credentialQueryWithDB(databaseService *gorm.DB, point models.DeliveryBasic) *gorm.DB {
|
||||
return common.ActiveRecords(databaseService.Model(&models.StaffCredential{})).
|
||||
Joins("JOIN staff_account ON staff_account.id = staff_credential.staff_account_id").
|
||||
Where("staff_account.delivery_basic_id = ? AND staff_account.gas_basic_id = ? AND staff_account.role_code = ?",
|
||||
point.ID, point.GasBasicID, "delivery")
|
||||
}
|
||||
|
||||
// scopedCredentialQuery 将资质列表进一步锁定到已验证的配送人员。
|
||||
func scopedCredentialQuery(databaseService *gorm.DB, point models.DeliveryBasic, staffID uint64) *gorm.DB {
|
||||
return credentialQueryWithDB(databaseService, point).
|
||||
Where("staff_credential.staff_account_id = ?", staffID)
|
||||
}
|
||||
|
||||
func ListCredential(ctx *gin.Context) {
|
||||
point, _, ok := currentScope(ctx)
|
||||
if ok {
|
||||
listScoped(ctx, &models.StaffCredential{}, credentialQuery(point), "staff_credential.created_at desc")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
staffIdentity := strings.TrimSpace(ctx.Query("staff_account_identity"))
|
||||
if staffIdentity == "" {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
staff, found := scopedStaff(ctx, staffIdentity, point)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
listScoped(ctx, &models.StaffCredential{}, scopedCredentialQuery(db(), point, staff.ID), "staff_credential.created_at desc")
|
||||
}
|
||||
|
||||
func GetCredential(ctx *gin.Context) {
|
||||
@@ -260,9 +282,13 @@ func UpdateCredential(ctx *gin.Context) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if staff.ID != existing.StaffAccountID {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
if err := db().Model(&existing).Updates(map[string]any{
|
||||
"staff_account_id": staff.ID, "credential_type": request.CredentialType,
|
||||
"credential_no": request.CredentialNo, "expired_at": request.ExpiredAt,
|
||||
"credential_type": request.CredentialType, "credential_no": request.CredentialNo,
|
||||
"expired_at": request.ExpiredAt,
|
||||
}).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
|
||||
@@ -31,3 +31,24 @@ func TestDeliveryStaffQueryKeepsRoleScope(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestScopedCredentialQueryKeepsOwnerAndRoleScope 验证资质列表同时限制人员和配送角色。
|
||||
func TestScopedCredentialQueryKeepsOwnerAndRoleScope(t *testing.T) {
|
||||
connection, _, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("创建 SQL mock 失败:%v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = connection.Close() })
|
||||
databaseService, err := gorm.Open(postgres.New(postgres.Config{Conn: connection}), &gorm.Config{DryRun: true})
|
||||
if err != nil {
|
||||
t.Fatalf("打开 GORM 失败:%v", err)
|
||||
}
|
||||
point := models.DeliveryBasic{Entity: models.Entity{ID: 22}, GasBasicID: 11}
|
||||
statement := scopedCredentialQuery(databaseService, point, 33).
|
||||
Find(&[]models.StaffCredential{}).Statement.SQL.String()
|
||||
for _, required := range []string{"delivery_basic_id", "gas_basic_id", "role_code", "staff_account_id"} {
|
||||
if !strings.Contains(statement, required) {
|
||||
t.Fatalf("配送人员资质范围缺少 %s:%s", required, statement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user