修复气站合同用户权限与服务关系边界
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
// 功能描述:为配送合同提供签约用户名称和当前服务关系状态,不新增数据库字段。
|
||||
// 版本:v1.0.0
|
||||
package gasorder
|
||||
|
||||
import (
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ContractPartyDisplay 在合同主档上附加可读用户名称和当前气站服务关系状态。
|
||||
type ContractPartyDisplay struct {
|
||||
models.GasorderContract
|
||||
UserAccountDisplayName string `gorm:"column:user_account_display_name" json:"user_account_display_name"`
|
||||
UserServiceActive bool `gorm:"column:user_service_active" json:"user_service_active"`
|
||||
}
|
||||
|
||||
// ContractPartyDisplayQuery 仅通过合同已保存的用户主键读取名称,不开放平台用户全集。
|
||||
func ContractPartyDisplayQuery(databaseService *gorm.DB) *gorm.DB {
|
||||
return common.ActiveRecords(databaseService.Model(&models.GasorderContract{})).
|
||||
Select(`gasorder_contract.*,
|
||||
COALESCE(NULLIF(contract_user.name, ''), NULLIF(contract_user.real_name, ''), NULLIF(contract_user.username, ''), '签约用户记录已失效') AS user_account_display_name,
|
||||
EXISTS (
|
||||
SELECT 1 FROM user_service_relation AS contract_relation
|
||||
WHERE contract_relation.user_account_id = gasorder_contract.user_account_id
|
||||
AND contract_relation.gas_basic_id = gasorder_contract.gas_basic_id
|
||||
AND contract_relation.status <> ?
|
||||
AND contract_relation.deleted_at IS NULL
|
||||
) AS user_service_active`, common.StatusArchived).
|
||||
Joins("LEFT JOIN user_account AS contract_user ON contract_user.id = gasorder_contract.user_account_id")
|
||||
}
|
||||
|
||||
// contractUserServiceActive 校验签约用户当前仍由合同气站服务。
|
||||
func contractUserServiceActive(databaseService *gorm.DB, contract models.GasorderContract) bool {
|
||||
return userServiceActiveForGas(databaseService, contract.UserAccountID, contract.GasBasicID)
|
||||
}
|
||||
|
||||
// userServiceActiveForGas 校验全局用户当前是否分配给指定气站。
|
||||
func userServiceActiveForGas(databaseService *gorm.DB, userAccountID, gasBasicID uint64) bool {
|
||||
var count int64
|
||||
err := databaseService.Model(&models.UserServiceRelation{}).
|
||||
Where("user_account_id = ? AND gas_basic_id = ? AND status <> ?", userAccountID, gasBasicID, common.StatusArchived).
|
||||
Count(&count).Error
|
||||
return err == nil && count > 0
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// 功能描述:实现平台配送合同、配送订单及履约状态动作。
|
||||
// 版本:v1.3.0
|
||||
// 版本:v1.4.0
|
||||
package gasorder
|
||||
|
||||
import (
|
||||
@@ -31,6 +31,7 @@ var (
|
||||
errContractNotEffective = errors.New("合同尚未到生效时间,暂时不能启用")
|
||||
errContractExpired = errors.New("合同已到期,请调整到期时间或续签后再启用")
|
||||
errContractNoProduct = errors.New("合同尚未绑定有效智能气阀。请先点击“绑定气阀”;若无可选设备,请到“智能气阀管理”将设备归属变更为合同用户,并确认设备已启用、未报废")
|
||||
errContractUserOutsideGas = errors.New("签约用户已不属于当前气站,只允许查看或终止合同")
|
||||
errBindingContractStatus = errors.New("仅草稿合同可以绑定气瓶")
|
||||
errBindingProductDisabled = errors.New("所选气瓶已停用,不能绑定到合同")
|
||||
errBindingProductScrapped = errors.New("所选气瓶已报废,不能绑定到合同")
|
||||
@@ -52,18 +53,38 @@ var (
|
||||
// ListGasorderContract 按明确用途收窄合同候选;合同管理列表不带用途参数时保持全量查询。
|
||||
func ListGasorderContract(ctx *gin.Context) {
|
||||
candidate := strings.TrimSpace(ctx.Query("candidate"))
|
||||
if candidate != "order" && candidate != "binding" {
|
||||
common.ListResource(ctx, &models.GasorderContract{})
|
||||
page, size := common.PageSize(ctx)
|
||||
query := common.ApplyKeywordFilter(ctx, ContractPartyDisplayQuery(impl.DBService), &models.GasorderContract{})
|
||||
if candidate == "order" || candidate == "binding" {
|
||||
query = filterGasorderContractCandidates(query, candidate, time.Now())
|
||||
}
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
common.ListPageFiltered[models.GasorderContract](ctx, func(query *gorm.DB) *gorm.DB {
|
||||
return filterGasorderContractCandidates(query, candidate, now)
|
||||
})
|
||||
var list []ContractPartyDisplay
|
||||
if err := query.Order("gasorder_contract.created_at desc").Offset((page - 1) * size).Limit(size).Scan(&list).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
response, err := common.PublicResourceResponse(list)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"total": total, "list": response})
|
||||
}
|
||||
|
||||
// filterGasorderContractCandidates 应用订单履约或草稿绑定场景的合同候选条件。
|
||||
func filterGasorderContractCandidates(query *gorm.DB, candidate string, now time.Time) *gorm.DB {
|
||||
// FilterGasorderContractCandidates 应用订单履约或草稿绑定场景的合同候选条件。
|
||||
func FilterGasorderContractCandidates(query *gorm.DB, candidate string, now time.Time) *gorm.DB {
|
||||
query = query.Where(`EXISTS (
|
||||
SELECT 1 FROM user_service_relation AS candidate_relation
|
||||
WHERE candidate_relation.user_account_id = gasorder_contract.user_account_id
|
||||
AND candidate_relation.gas_basic_id = gasorder_contract.gas_basic_id
|
||||
AND candidate_relation.status <> ?
|
||||
AND candidate_relation.deleted_at IS NULL
|
||||
)`, common.StatusArchived)
|
||||
if candidate == "binding" {
|
||||
return query.Where("contract_status = ?", common.StatusDraft)
|
||||
}
|
||||
@@ -74,6 +95,11 @@ func filterGasorderContractCandidates(query *gorm.DB, candidate string, now time
|
||||
now,
|
||||
)
|
||||
}
|
||||
|
||||
// filterGasorderContractCandidates 保留包内调用名称,统一委托给公开的范围过滤器。
|
||||
func filterGasorderContractCandidates(query *gorm.DB, candidate string, now time.Time) *gorm.DB {
|
||||
return FilterGasorderContractCandidates(query, candidate, now)
|
||||
}
|
||||
func GetGasorderContract(ctx *gin.Context) { getGasorderContract(ctx) }
|
||||
|
||||
// filterGasorderContractProductCandidates 仅保留当前合同仍有效绑定的气瓶,并按类型和编码排序。
|
||||
@@ -105,11 +131,16 @@ func ListGasorderPayment(ctx *gin.Context) { common.ListResource(ctx, &models
|
||||
func GetGasorderPayment(ctx *gin.Context) { common.GetResource(ctx, &models.GasorderPayment{}) }
|
||||
|
||||
func getGasorderContract(ctx *gin.Context) {
|
||||
var contract models.GasorderContract
|
||||
if err := common.ActiveRecords(impl.DBService).Where("identity = ?", ctx.Param("identity")).First(&contract).Error; err != nil {
|
||||
var display ContractPartyDisplay
|
||||
if err := ContractPartyDisplayQuery(impl.DBService).Where("gasorder_contract.identity = ?", ctx.Param("identity")).Scan(&display).Error; err != nil {
|
||||
common.RespondRecordError(ctx, err)
|
||||
return
|
||||
}
|
||||
contract := display.GasorderContract
|
||||
if contract.ID == 0 {
|
||||
common.RespondRecordError(ctx, gorm.ErrRecordNotFound)
|
||||
return
|
||||
}
|
||||
var products []gasorderContractProductDisplay
|
||||
var revisions []models.GasorderContractRevision
|
||||
if err := contractProductDisplayQuery(impl.DBService, "").
|
||||
@@ -124,7 +155,7 @@ func getGasorderContract(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
response, err := common.PublicResourceResponse(gin.H{
|
||||
"contract": contract, "products": products, "revisions": revisions,
|
||||
"contract": display, "products": products, "revisions": revisions,
|
||||
"attachment": contractAttachmentInfo(contract),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -238,6 +269,10 @@ func CreateGasorderContract(ctx *gin.Context) {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
if !userServiceActiveForGas(impl.DBService, userID, gasID) {
|
||||
infra.Response.Error(ctx, errContractUserOutsideGas)
|
||||
return
|
||||
}
|
||||
deliveryID, err := common.ResolveIdentityID(&models.DeliveryBasic{}, request.DeliveryIdentity, false)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
@@ -307,6 +342,10 @@ func UpdateGasorderContract(ctx *gin.Context) {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
if !contractUserServiceActive(impl.DBService, contract) {
|
||||
infra.Response.Error(ctx, errContractUserOutsideGas)
|
||||
return
|
||||
}
|
||||
if request.RemoveAttachment && request.AttachmentReceipt != "" {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
@@ -400,6 +439,9 @@ func RenewGasorderContract(ctx *gin.Context) {
|
||||
if contract.ContractStatus != common.StatusActive && contract.ContractStatus != common.StatusExpired && contract.ContractStatus != common.StatusTerminated {
|
||||
return errors.New("contract cannot be renewed")
|
||||
}
|
||||
if !contractUserServiceActive(tx, contract) {
|
||||
return errContractUserOutsideGas
|
||||
}
|
||||
if err := tx.Model(&contract).Updates(map[string]any{"contract_status": common.StatusActive, "effective_at": request.EffectiveAt, "expired_at": request.ExpiredAt}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -407,7 +449,11 @@ func RenewGasorderContract(ctx *gin.Context) {
|
||||
return tx.Create(contractRevision(contract, "renew", request.Reason, operatorIdentity, operatorName)).Error
|
||||
})
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
if errors.Is(err, errContractUserOutsideGas) {
|
||||
infra.Response.Error(ctx, err)
|
||||
} else {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
}
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"updated": true})
|
||||
@@ -430,6 +476,9 @@ func changeGasorderContract(ctx *gin.Context, action string, target int) {
|
||||
if action == "activate" && !contractCanActivate(contract.ContractStatus) {
|
||||
return errContractCannotActivate
|
||||
}
|
||||
if action == "activate" && !contractUserServiceActive(tx, contract) {
|
||||
return errContractUserOutsideGas
|
||||
}
|
||||
if action == "terminate" && contract.ContractStatus != common.StatusActive {
|
||||
return errContractCannotTerminate
|
||||
}
|
||||
@@ -483,7 +532,8 @@ func isContractChangeBusinessError(err error) bool {
|
||||
errors.Is(err, errContractAttachment) ||
|
||||
errors.Is(err, errContractNotEffective) ||
|
||||
errors.Is(err, errContractExpired) ||
|
||||
errors.Is(err, errContractNoProduct)
|
||||
errors.Is(err, errContractNoProduct) ||
|
||||
errors.Is(err, errContractUserOutsideGas)
|
||||
}
|
||||
|
||||
func contractRevision(contract models.GasorderContract, action, reason, operatorIdentity, operatorName string) *models.GasorderContractRevision {
|
||||
@@ -514,6 +564,10 @@ func BindGasorderContractProduct(ctx *gin.Context) {
|
||||
infra.Response.Error(ctx, errBindingContractStatus)
|
||||
return
|
||||
}
|
||||
if !contractUserServiceActive(impl.DBService, contract) {
|
||||
infra.Response.Error(ctx, errContractUserOutsideGas)
|
||||
return
|
||||
}
|
||||
var product models.ProductInfo
|
||||
if err := impl.DBService.Where("identity = ?", request.ProductIdentity).First(&product).Error; err != nil {
|
||||
common.RespondRecordError(ctx, err)
|
||||
@@ -567,6 +621,17 @@ func UnbindGasorderContractProduct(ctx *gin.Context) {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
var contract models.GasorderContract
|
||||
if err := impl.DBService.Model(&models.GasorderContract{}).
|
||||
Joins("JOIN gasorder_contract_product ON gasorder_contract_product.gasorder_contract_id = gasorder_contract.id").
|
||||
Where("gasorder_contract_product.identity = ?", ctx.Param("identity")).First(&contract).Error; err != nil {
|
||||
common.RespondRecordError(ctx, err)
|
||||
return
|
||||
}
|
||||
if !contractUserServiceActive(impl.DBService, contract) {
|
||||
infra.Response.Error(ctx, errContractUserOutsideGas)
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
result := impl.DBService.Model(&models.GasorderContractProduct{}).
|
||||
Where("identity = ? AND unbound_at IS NULL", ctx.Param("identity")).
|
||||
@@ -617,6 +682,9 @@ func CreateGasorderBasic(ctx *gin.Context) {
|
||||
if !contractEligibleForOrder(contract, now) {
|
||||
return errors.New("contract is not active")
|
||||
}
|
||||
if !contractUserServiceActive(tx, contract) {
|
||||
return errContractUserOutsideGas
|
||||
}
|
||||
var creatorStaff *models.StaffAccount
|
||||
if request.CreatorType == "staff" {
|
||||
var staff models.StaffAccount
|
||||
@@ -744,7 +812,8 @@ func isGasorderCreatorBusinessError(err error) bool {
|
||||
errors.Is(err, errOrderCreatorGas) ||
|
||||
errors.Is(err, errOrderCreatorNoDelivery) ||
|
||||
errors.Is(err, errOrderCreatorDelivery) ||
|
||||
errors.Is(err, errOrderCreatorStaff)
|
||||
errors.Is(err, errOrderCreatorStaff) ||
|
||||
errors.Is(err, errContractUserOutsideGas)
|
||||
}
|
||||
|
||||
// contractEligibleForOrder 校验合同当前确实处于可履约时间窗口。
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 功能描述:验证平台配送合同、订单创建和调度状态规则。
|
||||
// 版本:v1.2.0
|
||||
// 版本:v1.3.0
|
||||
package gasorder
|
||||
|
||||
import (
|
||||
@@ -63,7 +63,7 @@ func TestFilterGasorderContractCandidates(t *testing.T) {
|
||||
})
|
||||
}
|
||||
orderSQL := statement("order")
|
||||
for _, fragment := range []string{"contract_status = 11", "effective_at <=", "expired_at IS NOT NULL", "expired_at >"} {
|
||||
for _, fragment := range []string{"contract_status = 11", "effective_at <=", "expired_at IS NOT NULL", "expired_at >", "candidate_relation.user_account_id"} {
|
||||
if !strings.Contains(orderSQL, fragment) {
|
||||
t.Fatalf("订单合同候选缺少 %q:%s", fragment, orderSQL)
|
||||
}
|
||||
@@ -74,6 +74,36 @@ func TestFilterGasorderContractCandidates(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestContractPartyDisplayQuery 验证合同名称直接按合同用户主键读取,并计算同气站服务关系状态。
|
||||
func TestContractPartyDisplayQuery(t *testing.T) {
|
||||
sqlDatabase, _, 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)
|
||||
}
|
||||
statement := database.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||
var contracts []ContractPartyDisplay
|
||||
return ContractPartyDisplayQuery(tx).
|
||||
Where("gasorder_contract.gas_basic_id = ?", 42).
|
||||
Scan(&contracts)
|
||||
})
|
||||
for _, fragment := range []string{
|
||||
"LEFT JOIN user_account AS contract_user",
|
||||
"user_account_display_name",
|
||||
"contract_relation.user_account_id = gasorder_contract.user_account_id",
|
||||
"contract_relation.gas_basic_id = gasorder_contract.gas_basic_id",
|
||||
"gasorder_contract.gas_basic_id = 42",
|
||||
} {
|
||||
if !strings.Contains(statement, fragment) {
|
||||
t.Fatalf("合同签约用户展示 SQL 缺少 %q:%s", fragment, statement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFilterGasorderContractProductCandidates 验证订单候选仅包含当前合同未解绑气瓶并按业务字段排序。
|
||||
func TestFilterGasorderContractProductCandidates(t *testing.T) {
|
||||
sqlDatabase, _, err := sqlmock.New()
|
||||
|
||||
@@ -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