diff --git a/backend/api/internal/logic/delivery/order.go b/backend/api/internal/logic/delivery/order.go index 9e62fb5..9a4c993 100644 --- a/backend/api/internal/logic/delivery/order.go +++ b/backend/api/internal/logic/delivery/order.go @@ -1,5 +1,5 @@ // 功能描述:实现配送点范围内的合同、合同气瓶和配送订单接口。 -// 版本:v1.4.0。 +// 版本:v1.4.1。 package delivery import ( @@ -74,7 +74,8 @@ func ListContract(ctx *gin.Context) { infra.Response.Error(ctx, err) return } - var list []platformgasorder.ContractPartyDisplay + // 空结果也初始化为空切片,确保公开响应保持 JSON 数组而不是 null。 + list := make([]platformgasorder.ContractPartyDisplay, 0) 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 diff --git a/backend/api/internal/logic/delivery/order_contract_attachment_test.go b/backend/api/internal/logic/delivery/order_contract_attachment_test.go index 74f5f2a..0a1330f 100644 --- a/backend/api/internal/logic/delivery/order_contract_attachment_test.go +++ b/backend/api/internal/logic/delivery/order_contract_attachment_test.go @@ -1,5 +1,5 @@ // 功能描述:验证配送合同附件列表脱敏与公开状态投影。 -// 版本:v1.0.0。 +// 版本:v1.1.0。 package delivery import ( @@ -44,3 +44,22 @@ func TestProtectDeliveryContractListResponse(t *testing.T) { t.Fatal("无附件的合同必须返回 has_attachment=false") } } + +// TestProtectDeliveryContractListEmptyResponse 验证没有可用合同时仍返回空数组。 +func TestProtectDeliveryContractListEmptyResponse(t *testing.T) { + gin.SetMode(gin.TestMode) + ctx, _ := gin.CreateTestContext(httptest.NewRecorder()) + ctx.Request = httptest.NewRequest("GET", "/gasorder_contract?candidate=order", nil) + contracts := make([]models.GasorderContract, 0) + response, err := common.PublicResourceResponse(contracts) + if err != nil { + t.Fatalf("构造空合同公开响应失败: %v", err) + } + protected, err := protectDeliveryContractListResponse(ctx, response, contracts) + if err != nil { + t.Fatalf("空合同列表不应返回结构错误: %v", err) + } + if protected == nil || len(protected) != 0 { + t.Fatalf("空合同列表必须返回非 nil 空数组,实际值: %#v", protected) + } +} diff --git a/backend/api/internal/logic/delivery/user.go b/backend/api/internal/logic/delivery/user.go index 87d4c7f..2134beb 100644 --- a/backend/api/internal/logic/delivery/user.go +++ b/backend/api/internal/logic/delivery/user.go @@ -1,5 +1,5 @@ // 功能描述:实现配送点范围内的用户、服务关系和收货地址管理。 -// 版本:v1.1.0。 +// 版本:v1.2.0。 package delivery import ( @@ -12,9 +12,25 @@ import ( "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/upload" "git.apinb.com/heqiapp/platforms/backend/api/internal/models" "github.com/gin-gonic/gin" + "github.com/jackc/pgx/v5/pgconn" "gorm.io/gorm" ) +const deliveryUserUsernameConstraint = "idx_user_account_username" + +var errDeliveryUserUsernameExists = errors.New("用户名已存在,请更换用户名") + +// deliveryUserCreateError 仅转换配送点新建用户时的用户名唯一约束冲突。 +// 参数:err 为用户和服务关系事务返回的错误。 +// 返回值:用户名冲突返回中文提示,其他错误保持原样。 +func deliveryUserCreateError(err error) error { + var postgresError *pgconn.PgError + if errors.As(err, &postgresError) && postgresError.Code == "23505" && postgresError.ConstraintName == deliveryUserUsernameConstraint { + return errDeliveryUserUsernameExists + } + return err +} + func userQuery(pointID uint64) *gorm.DB { return common.ActiveRecords(db().Model(&models.UserAccount{})). Joins("JOIN user_service_relation ON user_service_relation.user_account_id = user_account.id AND user_service_relation.status <> ?", @@ -116,7 +132,7 @@ func CreateUser(ctx *gin.Context) { relation.UserAccountID = user.ID return tx.Create(&relation).Error }); err != nil { - infra.Response.Error(ctx, err) + infra.Response.Error(ctx, deliveryUserCreateError(err)) return } common.RespondCreatedResource(ctx, user) diff --git a/backend/api/internal/logic/delivery/user_error_test.go b/backend/api/internal/logic/delivery/user_error_test.go new file mode 100644 index 0000000..8d1b672 --- /dev/null +++ b/backend/api/internal/logic/delivery/user_error_test.go @@ -0,0 +1,33 @@ +// 功能描述:验证配送点新建用户的用户名冲突提示转换。 +// 版本:v1.0.0。 +package delivery + +import ( + "errors" + "fmt" + "testing" + + "github.com/jackc/pgx/v5/pgconn" +) + +// TestDeliveryUserCreateErrorUsernameConflict 验证用户名唯一约束冲突返回指定中文提示。 +// 参数:t 为 Go 测试上下文。 +// 返回值:无。 +func TestDeliveryUserCreateErrorUsernameConflict(t *testing.T) { + databaseError := &pgconn.PgError{Code: "23505", ConstraintName: deliveryUserUsernameConstraint} + wrapped := fmt.Errorf("create delivery user: %w", databaseError) + + if actual := deliveryUserCreateError(wrapped).Error(); actual != "用户名已存在,请更换用户名" { + t.Fatalf("用户名冲突提示不正确,实际为 %q", actual) + } +} + +// TestDeliveryUserCreateErrorKeepsOtherErrors 验证其他错误不被本次局部映射改变。 +// 参数:t 为 Go 测试上下文。 +// 返回值:无。 +func TestDeliveryUserCreateErrorKeepsOtherErrors(t *testing.T) { + original := errors.New("other error") + if actual := deliveryUserCreateError(original); !errors.Is(actual, original) { + t.Fatalf("其他错误应保持原样,实际为 %v", actual) + } +} diff --git a/docs/操作日志_配送点合同用户名称显示_20260831.md b/docs/操作日志_配送点合同用户名称显示_20260831.md new file mode 100644 index 0000000..db8383b --- /dev/null +++ b/docs/操作日志_配送点合同用户名称显示_20260831.md @@ -0,0 +1,29 @@ +# 操作日志:配送点合同用户名称显示 + +操作时间:2026-08-31 +操作类型:修改 +影响模块:配送点合同列表 + +## 操作前状态 + +“用户账户”列直接显示系统唯一标识片段,与气站管理端的用户名称展示不一致。 + +## 具体操作 + +1. 关系列表字段优先读取服务端派生业务名称。 +2. 复用名称、关联详情和唯一标识复制的组合展示。 +3. 增加静态契约检查,防止回退为裸唯一标识。 + +## 操作后状态 + +配送点合同列表按气站端样式显示用户业务名称,并保留完整唯一标识的复制能力。 + +## 验证结果 + +- `npm.cmd run type:check`:通过。 +- `npm.cmd run resource-pages:check`:通过,详情 17、新建 9、编辑 5。 +- `npm.cmd run build`:通过,2648 个模块完成生产构建。 + +## 风险评估 + +仅调整前端关系列表字段渲染,提交字段、后端响应和权限范围均不改变。 diff --git a/docs/操作日志_配送点用户名重复提示_20260831.md b/docs/操作日志_配送点用户名重复提示_20260831.md new file mode 100644 index 0000000..6c89771 --- /dev/null +++ b/docs/操作日志_配送点用户名重复提示_20260831.md @@ -0,0 +1,29 @@ +# 操作日志:配送点用户名重复提示 + +操作时间:2026-08-31 +操作类型:修改 +影响模块:配送点用户管理—新建用户 + +## 操作前状态 + +重复用户名直接向页面返回 PostgreSQL 唯一约束错误及 SQLSTATE。 + +## 具体操作 + +1. 识别用户名约束 `idx_user_account_username` 的 `23505` 冲突。 +2. 将该冲突转换为“用户名已存在,请更换用户名”。 +3. 其他数据库错误保持原有处理,避免扩大本次变更范围。 + +## 操作后状态 + +重复创建同名用户时页面显示明确中文提示,不再显示该约束的数据库英文错误。 + +## 验证结果 + +- `go test ./api/internal/logic/delivery`:通过。 +- 已覆盖包装后的 PostgreSQL `23505` 用户名约束冲突。 +- 已验证其他错误保持原样。 + +## 风险评估 + +仅改变一个已知唯一约束冲突的提示文本,不影响成功创建路径与其他错误类型。 diff --git a/docs/操作日志_配送点订单合同空列表修复_20260831.md b/docs/操作日志_配送点订单合同空列表修复_20260831.md new file mode 100644 index 0000000..0515f71 --- /dev/null +++ b/docs/操作日志_配送点订单合同空列表修复_20260831.md @@ -0,0 +1,30 @@ +# 操作日志:配送点订单合同空列表修复 + +操作时间:2026-08-31 +操作类型:修改 +影响模块:配送点订单创建、配送合同候选列表 + +## 操作前状态 + +没有可履约合同时,合同查询结果为 nil 切片,公开响应序列化成 `null`,随后被列表结构校验拒绝。 + +## 具体操作 + +1. 将合同查询接收容器初始化为空切片。 +2. 增加零条合同的回归测试,要求返回非 nil 空数组。 +3. 保持接口路径、筛选条件和响应字段不变。 + +## 操作后状态 + +无可用合同时接口正常返回 `total: 0`、`list: []`,页面只显示无可用合同提示。 + +## 验证结果 + +- `go test ./api/internal/logic/delivery`:通过。 +- 新增空合同列表用例,确认公开响应为非 nil 空数组。 +- 后端已重新构建并重启,12426 端口监听正常。 +- 浏览器会话的原登录令牌已失效并跳转登录页,登录后页面验证待执行。 + +## 风险评估 + +变更仅影响零条记录的序列化形式;有数据列表和合同筛选逻辑不变,兼容风险低。 diff --git a/docs/项目文档_配送点合同用户名称显示_v1.0.md b/docs/项目文档_配送点合同用户名称显示_v1.0.md new file mode 100644 index 0000000..f16cf42 --- /dev/null +++ b/docs/项目文档_配送点合同用户名称显示_v1.0.md @@ -0,0 +1,24 @@ +# 项目文档:配送点合同用户名称显示 v1.0 + +## 项目概述 + +配送点合同列表的“用户账户”列与气站管理端保持一致,以用户业务名称为主展示,不再把系统唯一标识作为主文本。 + +## 核心文件说明 + +- `frontend/delivery_admin/src/views/shared/ResourceListPage.vue`:渲染服务端返回的 `user_account_display_name`,并保留关联详情入口和唯一标识复制能力。 +- `frontend/delivery_admin/src/views/shared/RelationNameText.vue`:支持可点击的关系名称展示。 +- `frontend/delivery_admin/scripts/check-resource-pages.mjs`:增加关系名称展示的静态契约检查。 + +## 行为变化 + +- 修改前:合同列表显示用户账户唯一标识片段。 +- 修改后:合同列表显示用户业务名称,例如 `ccc`、`xc`、`cyy`;名称可进入用户详情,旁边按钮可复制完整唯一标识。 + +## 接口兼容性 + +复用现有合同列表响应中的 `user_account_display_name`,不修改后端接口和数据库。 + +## 测试方法 + +在 `frontend/delivery_admin` 目录执行类型检查、资源页面契约检查和生产构建。 diff --git a/docs/项目文档_配送点用户名重复提示_v1.0.md b/docs/项目文档_配送点用户名重复提示_v1.0.md new file mode 100644 index 0000000..d7c6066 --- /dev/null +++ b/docs/项目文档_配送点用户名重复提示_v1.0.md @@ -0,0 +1,22 @@ +# 项目文档:配送点用户名重复提示 v1.0 + +## 项目概述 + +配送点管理端新建用户时,将用户名唯一约束冲突转换为可理解的中文提示。 + +## 核心文件说明 + +- `backend/api/internal/logic/delivery/user.go`:识别 `idx_user_account_username` 唯一约束冲突并返回“用户名已存在,请更换用户名”。 +- `backend/api/internal/logic/delivery/user_error_test.go`:验证已知冲突提示与其他错误保持原样。 + +## 变更范围 + +仅修改配送点管理端新建用户的重复用户名提示,不修改数据库约束、用户创建流程、字段校验或其他错误处理。 + +## 测试方法 + +在 `backend` 目录执行: + +```text +go test ./api/internal/logic/delivery +``` diff --git a/docs/项目文档_配送点订单合同空列表修复_v1.0.md b/docs/项目文档_配送点订单合同空列表修复_v1.0.md new file mode 100644 index 0000000..33cbd56 --- /dev/null +++ b/docs/项目文档_配送点订单合同空列表修复_v1.0.md @@ -0,0 +1,27 @@ +# 项目文档:配送点订单合同空列表修复 v1.0 + +## 项目概述 + +修复配送点管理端创建配送订单时,当前没有可履约合同时错误提示“配送合同列表响应结构无效”的问题。 + +## 核心文件说明 + +- `backend/api/internal/logic/delivery/order.go`:查询配送合同候选项;空结果初始化为非 nil 空切片,保证接口输出 `list: []`。 +- `backend/api/internal/logic/delivery/order_contract_attachment_test.go`:覆盖合同空列表公开响应的回归测试。 + +## 行为变化 + +- 修复前:零条合同被序列化为 `null`,后端响应结构校验失败。 +- 修复后:零条合同返回空数组,前端正常显示“暂无可下单的生效合同”。 + +## 接口兼容性 + +接口地址和字段保持不变,仅将异常的 `list: null` 规范为 `list: []`,兼容现有调用方。 + +## 测试方法 + +在 `backend` 目录执行: + +```text +go test ./api/internal/logic/delivery +``` diff --git a/frontend/delivery_admin/scripts/check-resource-pages.mjs b/frontend/delivery_admin/scripts/check-resource-pages.mjs index 7397f4f..5e0fda4 100644 --- a/frontend/delivery_admin/scripts/check-resource-pages.mjs +++ b/frontend/delivery_admin/scripts/check-resource-pages.mjs @@ -74,6 +74,9 @@ const listPage = read('src/views/shared/ResourceListPage.vue'); assert(!listPage.includes('title="ID"'), '标准列表仍暴露数据库自增 ID'); assert(listPage.includes('ProtectedAvatarThumbnail'), '账户列表尚未接入受控头像缩略图'); assert(listPage.includes('avatarLoader.reset()'), '列表刷新未清理头像缓存和请求'); +assert(listPage.includes('field.listRelationNameOnly && fieldValue(field, record)'), '关系列表字段尚未按气站端显示业务名称'); +assert(listPage.includes('relationListName(field, record)'), '关系列表字段缺少服务端派生名称读取'); +assert(listPage.includes('@open="openRelation(field, record)"'), '关系列表名称缺少关联详情入口'); const avatarLoader = read('src/views/shared/protected-list-avatar-loader.ts'); assert(avatarLoader.includes('MAX_CONCURRENT_REQUESTS = 6'), '头像请求并发上限未与 5173 对齐'); assert(avatarLoader.includes("new Set(['staff_account', 'user_account'])"), '头像列表资源白名单不正确'); diff --git a/frontend/delivery_admin/src/views/shared/RelationNameText.vue b/frontend/delivery_admin/src/views/shared/RelationNameText.vue index 084c55b..7926282 100644 --- a/frontend/delivery_admin/src/views/shared/RelationNameText.vue +++ b/frontend/delivery_admin/src/views/shared/RelationNameText.vue @@ -1,8 +1,14 @@ - +