修复库房地址受控展示
为平台库房管理列表和详情增加专用响应处理,在保留全局敏感字段保护的前提下恢复库房自身地址。未填写地址时显示‘未填写’,创建和编辑继续复用原有白名单写入逻辑,不修改数据库及历史数据。补充回归测试、操作日志和项目文档。
This commit is contained in:
71
backend/api/internal/logic/platform/product/warehouse.go
Normal file
71
backend/api/internal/logic/platform/product/warehouse.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// 功能描述:提供平台总后台库房列表与详情,并受控恢复库房自身地址。
|
||||
// 版本:v1.0
|
||||
package product
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/infra"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ListProductWarehouse 查询库房分页列表,并向已鉴权的平台管理端恢复库房地址。
|
||||
func ListProductWarehouse(ctx *gin.Context) {
|
||||
page, size := common.PageSize(ctx)
|
||||
var list []models.ProductWarehouse
|
||||
var total int64
|
||||
query := common.ApplyKeywordFilter(ctx, common.ActiveRecords(impl.DBService.Model(&models.ProductWarehouse{})), &models.ProductWarehouse{})
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
if err := query.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
response, err := common.PublicResourceResponse(list)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
// 先执行全局敏感字段保护,再仅为库房管理资源恢复库房自身地址。
|
||||
protected := common.ProtectPreciseLocation(ctx, &models.ProductWarehouse{}, response)
|
||||
infra.Response.Success(ctx, gin.H{"total": total, "list": restoreProductWarehouseAddresses(protected, list)})
|
||||
}
|
||||
|
||||
// GetProductWarehouse 查询单个库房,并返回详情与编辑表单所需的库房地址。
|
||||
func GetProductWarehouse(ctx *gin.Context) {
|
||||
var warehouse models.ProductWarehouse
|
||||
if err := common.ActiveRecords(impl.DBService).Where("identity = ?", ctx.Param("identity")).First(&warehouse).Error; err != nil {
|
||||
common.RespondRecordError(ctx, err)
|
||||
return
|
||||
}
|
||||
response, err := common.PublicResourceResponse(warehouse)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
protected := common.ProtectPreciseLocation(ctx, &models.ProductWarehouse{}, response)
|
||||
infra.Response.Success(ctx, restoreProductWarehouseAddresses(protected, []models.ProductWarehouse{warehouse}))
|
||||
}
|
||||
|
||||
// restoreProductWarehouseAddresses 将已鉴权库房记录的地址恢复到安全响应中。
|
||||
func restoreProductWarehouseAddresses(response any, warehouses []models.ProductWarehouse) any {
|
||||
switch data := response.(type) {
|
||||
case []any:
|
||||
for index, item := range data {
|
||||
if index >= len(warehouses) {
|
||||
break
|
||||
}
|
||||
if record, ok := item.(map[string]any); ok {
|
||||
record["address"] = warehouses[index].Address
|
||||
}
|
||||
}
|
||||
case map[string]any:
|
||||
if len(warehouses) > 0 {
|
||||
data["address"] = warehouses[0].Address
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// 功能描述:验证库房管理响应仅恢复库房自身地址。
|
||||
// 版本:v1.0
|
||||
package product
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||
)
|
||||
|
||||
// TestRestoreProductWarehouseAddresses 验证库房列表按原顺序恢复地址。
|
||||
func TestRestoreProductWarehouseAddresses(t *testing.T) {
|
||||
response := []any{
|
||||
map[string]any{"identity": "warehouse-1"},
|
||||
map[string]any{"identity": "warehouse-2"},
|
||||
}
|
||||
warehouses := []models.ProductWarehouse{
|
||||
{Address: "海口市龙华区库房路 1 号"},
|
||||
{Address: "三亚市吉阳区库房路 2 号"},
|
||||
}
|
||||
|
||||
restored := restoreProductWarehouseAddresses(response, warehouses).([]any)
|
||||
for index, warehouse := range warehouses {
|
||||
if restored[index].(map[string]any)["address"] != warehouse.Address {
|
||||
t.Fatalf("第 %d 条库房地址未正确恢复:%#v", index+1, restored[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRestoreProductWarehouseAddress 验证库房详情恢复编辑所需地址。
|
||||
func TestRestoreProductWarehouseAddress(t *testing.T) {
|
||||
response := map[string]any{"identity": "warehouse-1"}
|
||||
warehouse := models.ProductWarehouse{Address: "海口市秀英区库房路 18 号"}
|
||||
|
||||
restored := restoreProductWarehouseAddresses(response, []models.ProductWarehouse{warehouse}).(map[string]any)
|
||||
if restored["address"] != warehouse.Address {
|
||||
t.Fatalf("库房详情地址未正确恢复:%#v", restored)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRestoreProductWarehouseEmptyAddress 验证未填写地址时保留空值。
|
||||
func TestRestoreProductWarehouseEmptyAddress(t *testing.T) {
|
||||
response := map[string]any{"identity": "warehouse-1"}
|
||||
|
||||
restored := restoreProductWarehouseAddresses(response, []models.ProductWarehouse{{}}).(map[string]any)
|
||||
if restored["address"] != "" {
|
||||
t.Fatalf("未填写库房地址应保留空值:%#v", restored)
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,9 @@ func registerProductRoute(group *gin.RouterGroup) {
|
||||
producer.DELETE("/:identity", product.DeleteProducerAccount)
|
||||
|
||||
registerRestrictedNoDeleteResource(group, "/product_type", &models.ProductType{}, []string{"code", "name"})
|
||||
registerRestrictedNoDeleteResource(group, "/product_warehouse", &models.ProductWarehouse{}, []string{"code", "name", "address", "manager", "phone"})
|
||||
warehouseFields := []string{"code", "name", "address", "manager", "phone"}
|
||||
_, warehouseCreate, _, warehouseUpdate := common.ResourceHandlers(&models.ProductWarehouse{}, warehouseFields, warehouseFields)
|
||||
registerNoDeleteResource(group, "/product_warehouse", product.ListProductWarehouse, warehouseCreate, product.GetProductWarehouse, warehouseUpdate, &models.ProductWarehouse{})
|
||||
|
||||
infoRelations := []common.ResourceRelation{
|
||||
requiredRelation("producer_account_identity", "producer_account_id", &models.ProducerAccount{}),
|
||||
|
||||
43
docs/操作日志_库房地址受控展示_20260813.md
Normal file
43
docs/操作日志_库房地址受控展示_20260813.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# 库房地址受控展示操作日志
|
||||
|
||||
操作时间:2026-08-13
|
||||
操作类型:修改
|
||||
影响模块:平台总后台库房管理、平台管理 API
|
||||
|
||||
## 操作前状态
|
||||
|
||||
库房地址已保存于 `product_warehouse.address`,新建与编辑接口也允许写入地址,但通用敏感字段保护会删除列表和详情响应中的 `address`,导致页面统一显示 `-`。
|
||||
|
||||
## 具体操作
|
||||
|
||||
1. 为库房管理增加专用列表与详情处理器。
|
||||
2. 保留全局敏感字段保护,并在保护后仅恢复当前库房资源自身的地址。
|
||||
3. 创建与编辑继续复用原有通用白名单写入逻辑。
|
||||
4. 未填写地址时在页面显示“未填写”。
|
||||
5. 新增列表、详情和空地址回归测试。
|
||||
|
||||
## 操作后状态
|
||||
|
||||
- 库房列表、详情和编辑页面可取得已保存地址。
|
||||
- 未填写地址时显示“未填写”。
|
||||
- 其他资源和公共接口继续执行原有地址保护。
|
||||
- 数据库结构与历史数据均未修改。
|
||||
|
||||
## 代码变更
|
||||
|
||||
- `backend/api/internal/logic/platform/product/warehouse.go`:新增库房地址受控列表、详情和恢复逻辑。
|
||||
- `backend/api/internal/logic/platform/product/warehouse_test.go`:新增地址恢复测试。
|
||||
- `backend/api/internal/routers/platform.go`:库房 GET 路由改用专用处理器,POST/PUT 保持通用处理器。
|
||||
- `frontend/platform_admin/src/api/resources.ts`:配置库房地址空值文案。
|
||||
|
||||
## 验证结果
|
||||
|
||||
- `go test ./internal/logic/platform/product ./internal/routers`:通过。
|
||||
- `npm.cmd run resource-pages:check`:通过,详情 46 类、新建 25 类、编辑 23 类。
|
||||
- `npm.cmd run build`:通过,TypeScript 检查与 Vite 生产构建成功。
|
||||
- 本地后端重新编译并重启,健康接口返回 `platform-api`、`ok`。
|
||||
- `git diff --check`:通过。
|
||||
|
||||
## 风险评估
|
||||
|
||||
完整库房地址仅在已鉴权的平台库房管理接口中恢复。全局敏感字段规则未放宽,数据库与其他终端不受影响。
|
||||
39
docs/项目文档_库房地址受控展示_v1.0.md
Normal file
39
docs/项目文档_库房地址受控展示_v1.0.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 项目文档:库房地址受控展示 v1.0
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
本次修改解决平台总后台库房地址已保存但无法显示的问题。库房继续使用自身的 `product_warehouse.address` 字段,地址保持可选,不修改数据库结构或历史数据。
|
||||
|
||||
## 2. 目录结构说明
|
||||
|
||||
```text
|
||||
platforms/
|
||||
├── backend/api/internal/logic/platform/product/
|
||||
│ ├── warehouse.go # 库房列表、详情及地址受控恢复
|
||||
│ └── warehouse_test.go # 库房地址响应测试
|
||||
├── backend/api/internal/routers/
|
||||
│ └── platform.go # 平台库房管理路由
|
||||
├── frontend/platform_admin/src/api/
|
||||
│ └── resources.ts # 库房地址空值展示配置
|
||||
└── docs/
|
||||
├── 项目文档_库房地址受控展示_v1.0.md
|
||||
└── 操作日志_库房地址受控展示_20260813.md
|
||||
```
|
||||
|
||||
## 3. 核心文件说明
|
||||
|
||||
- `warehouse.go`:列表与详情先执行通用响应投影和敏感字段保护,再按当前查询记录恢复库房自身地址。
|
||||
- `warehouse_test.go`:覆盖列表顺序、单条详情及空地址场景。
|
||||
- `platform.go`:GET 使用库房专用处理器;创建、更新和状态修改继续沿用通用资源能力。
|
||||
- `resources.ts`:空地址显示“未填写”,编辑输入提示为“请输入地址”。
|
||||
|
||||
## 4. 变更记录
|
||||
|
||||
- 修复库房列表地址显示 `-`。
|
||||
- 支持详情和编辑页面读取已保存库房地址。
|
||||
- 保持地址可选,并为未填写状态提供明确文案。
|
||||
- 未新增依赖、数据库迁移或历史数据修改。
|
||||
|
||||
## 5. 维护指南
|
||||
|
||||
新增需要受控展示地址的管理资源时,应采用资源专用恢复逻辑,不得从全局敏感字段集合中移除 `address`。修改后至少运行平台产品逻辑测试、路由测试和平台前端构建。
|
||||
@@ -446,7 +446,7 @@ export const resources: ResourceUiDefinition[] = [
|
||||
|
||||
define('producer_account', '生产商管理', 'writable', [f('producer_code', { required: true }), f('name', { required: true }), f('credit_code'), f('principal'), f('phone'), f('address', { emptyText: '未填写', placeholder: '请输入地址' }), f('username', { required: true }), f('password', { required: true }), f('display_name'), producerAdminRole(), f('remark')]),
|
||||
define('product_type', '智能气阀类型', 'editable', [f('code', { required: true }), f('name', { required: true })]),
|
||||
define('product_warehouse', '智能气阀库房', 'editable', [f('code', { required: true }), f('name', { required: true }), f('address'), f('manager'), f('phone')]),
|
||||
define('product_warehouse', '智能气阀库房', 'editable', [f('code', { required: true }), f('name', { required: true }), f('address', { emptyText: '未填写', placeholder: '请输入地址' }), f('manager'), f('phone')]),
|
||||
define('product_info', '智能气阀', 'editable', [f('code', { required: true }), f('name', { required: true }), relation('producer_account_identity', '/producer_account', true), relation('product_type_identity', '/product_type', true), f('params', { required: true }), relation('warehouse_identity', '/product_warehouse'), relation('gas_basic_identity', '/gas_basic'), relation('delivery_basic_identity', '/delivery_basic'), relation('user_account_identity', '/user_account'), f('produced_at', { required: true })], 'list', [
|
||||
{ name: '修改智能气阀状态', resource: '/product_info/:identity/lifecycle', method: 'PATCH', fields: [f('product_status', { required: true, type: 'select', options: [{ label: '待处理', value: 10 }, { label: '在库', value: 28 }, { label: '运输中', value: 29 }, { label: '使用中', value: 30 }, { label: '维修中', value: 31 }, { label: '已报废', value: 27 }] })] },
|
||||
{ name: '变更智能气阀归属', resource: '/product_info/:identity', method: 'PUT', fields: [relation('warehouse_identity', '/product_warehouse'), relation('gas_basic_identity', '/gas_basic'), relation('delivery_basic_identity', '/delivery_basic'), relation('user_account_identity', '/user_account'), f('action', { required: true, type: 'select', options: [{ label: '入库', value: 'warehouse' }, { label: '分配', value: 'assigned' }, { label: '归还', value: 'returned' }, { label: '人工调整', value: 'manual' }] }), f('reason', { required: true }), f('remark')] },
|
||||
|
||||
Reference in New Issue
Block a user