修复气站合同用户权限与服务关系边界
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// 功能描述:实现平台总后台的用户地址与服务关系管理。
|
||||
// 版本:v1.1
|
||||
// 版本:v1.2
|
||||
package user
|
||||
|
||||
import (
|
||||
@@ -176,6 +176,8 @@ type serviceRelationRequest struct {
|
||||
StaffAccountIdentity string `json:"staff_account_identity"`
|
||||
}
|
||||
|
||||
var errUserServiceTransferBlocked = errors.New("用户存在生效合同、未完成订单或未关闭工单,请处理完成后再变更所属气站")
|
||||
|
||||
func ListUserServiceRelation(ctx *gin.Context) { common.ListPage[models.UserServiceRelation](ctx) }
|
||||
func GetUserServiceRelation(ctx *gin.Context) { common.GetByIdentity[models.UserServiceRelation](ctx) }
|
||||
func CreateUserServiceRelation(ctx *gin.Context) {
|
||||
@@ -206,7 +208,7 @@ func UpdateUserServiceRelation(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
var current models.UserServiceRelation
|
||||
if err := common.ActiveRecords(impl.DBService).Select("user_account_id").
|
||||
if err := common.ActiveRecords(impl.DBService).Select("user_account_id", "gas_basic_id").
|
||||
Where("identity = ?", ctx.Param("identity")).First(¤t).Error; err != nil {
|
||||
common.RespondRecordError(ctx, err)
|
||||
return
|
||||
@@ -215,9 +217,88 @@ func UpdateUserServiceRelation(ctx *gin.Context) {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
if current.GasBasicID != gasBasicID {
|
||||
blocked, err := userServiceTransferBlocked(impl.DBService, current)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
if blocked {
|
||||
infra.Response.Error(ctx, errUserServiceTransferBlocked)
|
||||
return
|
||||
}
|
||||
}
|
||||
common.UpdateAllowedByIdentity(ctx, &models.UserServiceRelation{}, gin.H{"user_account_id": userAccountID, "gas_basic_id": gasBasicID, "delivery_basic_id": deliveryBasicID, "staff_account_id": staffAccountID}, []string{"user_account_id", "gas_basic_id", "delivery_basic_id", "staff_account_id"})
|
||||
}
|
||||
|
||||
// UpdateUserServiceRelationStatus 更新服务关系状态;归档必须执行履约阻断检查。
|
||||
func UpdateUserServiceRelationStatus(ctx *gin.Context) {
|
||||
var request struct {
|
||||
Status int `json:"status" binding:"required"`
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil || !common.IsGenericRecordStatus(request.Status) {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
if request.Status == common.StatusArchived && !allowUserServiceRelationRemoval(ctx) {
|
||||
return
|
||||
}
|
||||
common.UpdateAllowedByIdentity(ctx, &models.UserServiceRelation{}, gin.H{"status": request.Status}, []string{"status"})
|
||||
}
|
||||
|
||||
// ArchiveUserServiceRelation 归档服务关系;存在未完成履约业务时拒绝解除归属。
|
||||
func ArchiveUserServiceRelation(ctx *gin.Context) {
|
||||
if !allowUserServiceRelationRemoval(ctx) {
|
||||
return
|
||||
}
|
||||
common.UpdateAllowedByIdentity(ctx, &models.UserServiceRelation{}, gin.H{"status": common.StatusArchived}, []string{"status"})
|
||||
}
|
||||
|
||||
// allowUserServiceRelationRemoval 校验当前服务关系是否允许解除。
|
||||
func allowUserServiceRelationRemoval(ctx *gin.Context) bool {
|
||||
var current models.UserServiceRelation
|
||||
if err := common.ActiveRecords(impl.DBService).Where("identity = ?", ctx.Param("identity")).First(¤t).Error; err != nil {
|
||||
common.RespondRecordError(ctx, err)
|
||||
return false
|
||||
}
|
||||
blocked, err := userServiceTransferBlocked(impl.DBService, current)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return false
|
||||
}
|
||||
if blocked {
|
||||
infra.Response.Error(ctx, errUserServiceTransferBlocked)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// userServiceTransferBlocked 检查原气站仍在履约中的合同、订单和工单。
|
||||
func userServiceTransferBlocked(databaseService *gorm.DB, relation models.UserServiceRelation) (bool, error) {
|
||||
if relation.GasBasicID == 0 {
|
||||
return false, nil
|
||||
}
|
||||
checks := []struct {
|
||||
model any
|
||||
where string
|
||||
args []any
|
||||
}{
|
||||
{&models.GasorderContract{}, "user_account_id = ? AND gas_basic_id = ? AND status <> ? AND contract_status = ?", []any{relation.UserAccountID, relation.GasBasicID, common.StatusArchived, common.StatusActive}},
|
||||
{&models.GasorderBasic{}, "user_account_id = ? AND gas_basic_id = ? AND status <> ? AND order_status NOT IN ?", []any{relation.UserAccountID, relation.GasBasicID, common.StatusArchived, []int{common.StatusCompleted, common.StatusCancelled}}},
|
||||
{&models.CsTicket{}, "user_account_id = ? AND gas_basic_id = ? AND status <> ? AND ticket_status = ?", []any{relation.UserAccountID, relation.GasBasicID, common.StatusArchived, common.StatusOpen}},
|
||||
}
|
||||
for _, check := range checks {
|
||||
var count int64
|
||||
if err := databaseService.Model(check.model).Where(check.where, check.args...).Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
if count > 0 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func resolveServiceRelation(ctx *gin.Context, request serviceRelationRequest) (uint64, uint64, uint64, uint64, bool) {
|
||||
userAccountID, err := common.ResolveIdentityID(&models.UserAccount{}, request.UserAccountIdentity, true)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 功能描述:验证平台用户地址写入参数与 GORM 的兼容性。
|
||||
// 版本:v1.1
|
||||
// 版本:v1.2
|
||||
package user
|
||||
|
||||
import (
|
||||
@@ -42,6 +42,52 @@ func TestFilterUserAddressByContract(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestUserServiceTransferBlocked 验证生效合同会阻止总后台转移用户所属气站。
|
||||
func TestUserServiceTransferBlocked(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
counts []int64
|
||||
want bool
|
||||
}{
|
||||
{"生效合同", []int64{1}, true},
|
||||
{"未完成订单", []int64{0, 1}, true},
|
||||
{"未关闭工单", []int64{0, 0, 1}, true},
|
||||
{"无阻断业务", []int64{0, 0, 0}, false},
|
||||
}
|
||||
patterns := []string{
|
||||
`SELECT count\(\*\) FROM "gasorder_contract"`,
|
||||
`SELECT count\(\*\) FROM "gasorder_basic"`,
|
||||
`SELECT count\(\*\) FROM "cs_ticket"`,
|
||||
}
|
||||
for _, test := range cases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
sqlDatabase, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("创建模拟数据库失败:%v", err)
|
||||
}
|
||||
defer sqlDatabase.Close()
|
||||
database, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDatabase}), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("创建 GORM 数据库失败:%v", err)
|
||||
}
|
||||
for index, count := range test.counts {
|
||||
mock.ExpectQuery(patterns[index]).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(count))
|
||||
}
|
||||
blocked, err := userServiceTransferBlocked(database, models.UserServiceRelation{UserAccountID: 7, GasBasicID: 9})
|
||||
if err != nil {
|
||||
t.Fatalf("检查转移阻断失败:%v", err)
|
||||
}
|
||||
if blocked != test.want {
|
||||
t.Fatalf("转移阻断结果 = %v,期望 %v", blocked, test.want)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("数据库预期未满足:%v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRestoreUserAddressLocations 验证用户地址列表按原顺序恢复地址与坐标。
|
||||
func TestRestoreUserAddressLocations(t *testing.T) {
|
||||
response := []any{
|
||||
|
||||
Reference in New Issue
Block a user