优化合同气瓶设备名称显示

This commit is contained in:
czl231
2026-08-14 01:49:44 +08:00
parent 08f1a917b3
commit 14263416e0
11 changed files with 400 additions and 46 deletions

View File

@@ -68,34 +68,14 @@ func filterGasorderContractCandidates(query *gorm.DB, candidate string, now time
}
func GetGasorderContract(ctx *gin.Context) { getGasorderContract(ctx) }
// ListGasorderContractProduct 在订单候选场景按合同收窄未解绑气瓶,未传合同时保留完整历史列表。
func ListGasorderContractProduct(ctx *gin.Context) {
contractIdentity := strings.TrimSpace(ctx.Query("contract_identity"))
if contractIdentity == "" {
common.ListResource(ctx, &models.GasorderContractProduct{})
return
}
contractID, err := common.ResolveIdentityID(&models.GasorderContract{}, contractIdentity, true)
if err != nil {
common.RespondRecordError(ctx, err)
return
}
common.ListPageFiltered[models.GasorderContractProduct](ctx, func(query *gorm.DB) *gorm.DB {
return filterGasorderContractProductCandidates(query, contractID)
})
}
// filterGasorderContractProductCandidates 仅保留当前合同仍有效绑定的气瓶,并按类型和编码排序。
func filterGasorderContractProductCandidates(query *gorm.DB, contractID uint64) *gorm.DB {
return query.
Where("gasorder_contract_id = ? AND unbound_at IS NULL", contractID).
Order("product_type_name asc").
Order("product_code asc")
Where("gasorder_contract_product.gasorder_contract_id = ? AND gasorder_contract_product.unbound_at IS NULL", contractID).
Order("gasorder_contract_product.product_type_name asc").
Order("gasorder_contract_product.product_code asc")
}
func GetGasorderContractProduct(ctx *gin.Context) {
common.GetResource(ctx, &models.GasorderContractProduct{})
}
func ListGasorderContractRevision(ctx *gin.Context) {
common.ListResource(ctx, &models.GasorderContractRevision{})
}
@@ -104,8 +84,6 @@ func GetGasorderContractRevision(ctx *gin.Context) {
}
func ListGasorderBasic(ctx *gin.Context) { common.ListResource(ctx, &models.GasorderBasic{}) }
func GetGasorderBasic(ctx *gin.Context) { getGasorderBasic(ctx) }
func ListGasorderItem(ctx *gin.Context) { common.ListResource(ctx, &models.GasorderItem{}) }
func GetGasorderItem(ctx *gin.Context) { common.GetResource(ctx, &models.GasorderItem{}) }
func ListGasorderAssign(ctx *gin.Context) { common.ListResource(ctx, &models.GasorderAssign{}) }
func GetGasorderAssign(ctx *gin.Context) { common.GetResource(ctx, &models.GasorderAssign{}) }
func ListGasorderStatus(ctx *gin.Context) { common.ListResource(ctx, &models.GasorderStatus{}) }
@@ -125,9 +103,12 @@ func getGasorderContract(ctx *gin.Context) {
common.RespondRecordError(ctx, err)
return
}
var products []models.GasorderContractProduct
var products []gasorderContractProductDisplay
var revisions []models.GasorderContractRevision
if err := impl.DBService.Where("gasorder_contract_id = ?", contract.ID).Order("bound_at asc, id asc").Find(&products).Error; err != nil {
if err := contractProductDisplayQuery(impl.DBService, "").
Where("gasorder_contract_product.gasorder_contract_id = ?", contract.ID).
Order("gasorder_contract_product.bound_at asc, gasorder_contract_product.id asc").
Scan(&products).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
@@ -152,17 +133,23 @@ func getGasorderBasic(ctx *gin.Context) {
common.RespondRecordError(ctx, err)
return
}
var items []models.GasorderItem
var items []gasorderItemDisplay
var assignments []models.GasorderAssign
var statuses []models.GasorderStatus
var tracks []models.GasorderTrack
var confirmations []models.GasorderConfirm
var payments []models.GasorderPayment
if err := gasorderItemDisplayQuery(impl.DBService, "").
Where("gasorder_item.gasorder_basic_id = ?", order.ID).
Order("gasorder_item.id asc").Scan(&items).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
loaders := []struct {
order string
value any
}{
{"id asc", &items}, {"assigned_at asc, id asc", &assignments},
{"assigned_at asc, id asc", &assignments},
{"occurred_at asc, id asc", &statuses}, {"attempt_no asc, id asc", &tracks},
{"confirmed_at asc, id asc", &confirmations}, {"attempt_no asc, id asc", &payments},
}

View File

@@ -89,9 +89,9 @@ func TestFilterGasorderContractProductCandidates(t *testing.T) {
).Find(&products)
})
for _, fragment := range []string{
"gasorder_contract_id = 27",
"unbound_at IS NULL",
"ORDER BY product_type_name asc,product_code asc",
"gasorder_contract_product.gasorder_contract_id = 27",
"gasorder_contract_product.unbound_at IS NULL",
"ORDER BY gasorder_contract_product.product_type_name asc,gasorder_contract_product.product_code asc",
} {
if !strings.Contains(statement, fragment) {
t.Fatalf("合同气瓶候选缺少 %q%s", fragment, statement)

View File

@@ -0,0 +1,155 @@
// 功能描述:为合同气瓶和订单明细补充当前智能气阀名称,并保持合同类型、编码快照不变。
// 版本v1.0.0
package gasorder
import (
"strings"
"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"
"gorm.io/gorm"
)
// gasorderContractProductDisplay 是合同绑定快照与当前智能气阀名称组成的只读响应。
type gasorderContractProductDisplay struct {
models.GasorderContractProduct
ProductName string `gorm:"column:product_name" json:"product_name"` // 当前智能气阀名称,仅用于可读展示
}
// gasorderItemDisplay 是订单气瓶快照与当前智能气阀名称组成的只读响应。
type gasorderItemDisplay struct {
models.GasorderItem
ProductName string `gorm:"column:product_name" json:"product_name"` // 当前智能气阀名称,仅用于可读展示
}
// contractProductDisplayQuery 关联未归档的当前智能气阀档案,并支持按名称、编码或绑定类型搜索。
func contractProductDisplayQuery(database *gorm.DB, keyword string) *gorm.DB {
query := common.ActiveRecords(database.Model(&models.GasorderContractProduct{})).
Select("gasorder_contract_product.*, COALESCE(display_product.name, '') AS product_name").
Joins("LEFT JOIN product_info AS display_product ON display_product.id = gasorder_contract_product.product_info_id AND display_product.deleted_at IS NULL AND display_product.status <> ?", common.StatusArchived)
return applyProductDisplayKeyword(query, keyword, "gasorder_contract_product")
}
// gasorderItemDisplayQuery 关联未归档的当前智能气阀档案,并保留订单创建时的编码与类型快照。
func gasorderItemDisplayQuery(database *gorm.DB, keyword string) *gorm.DB {
query := common.ActiveRecords(database.Model(&models.GasorderItem{})).
Select("gasorder_item.*, COALESCE(display_product.name, '') AS product_name").
Joins("LEFT JOIN product_info AS display_product ON display_product.id = gasorder_item.product_info_id AND display_product.deleted_at IS NULL AND display_product.status <> ?", common.StatusArchived)
return applyProductDisplayKeyword(query, keyword, "gasorder_item")
}
// applyProductDisplayKeyword 对可信固定列执行模糊搜索,不拼接用户输入到 SQL 结构中。
func applyProductDisplayKeyword(query *gorm.DB, keyword, table string) *gorm.DB {
keyword = strings.ToLower(strings.TrimSpace(keyword))
if keyword == "" {
return query
}
pattern := "%" + keyword + "%"
return query.Where(
"(LOWER(COALESCE(display_product.name, '')) LIKE ? OR LOWER("+table+".product_code) LIKE ? OR LOWER("+table+".product_type_name) LIKE ?)",
pattern, pattern, pattern,
)
}
// ListGasorderContractProduct 返回合同气瓶历史;订单候选场景仅返回指定合同尚未解绑的记录。
func ListGasorderContractProduct(ctx *gin.Context) {
contractIdentity := strings.TrimSpace(ctx.Query("contract_identity"))
var contractID uint64
var err error
if contractIdentity != "" {
contractID, err = common.ResolveIdentityID(&models.GasorderContract{}, contractIdentity, true)
if err != nil {
common.RespondRecordError(ctx, err)
return
}
}
page, size := common.PageSize(ctx)
query := contractProductDisplayQuery(impl.DBService, ctx.Query("keyword"))
if contractID > 0 {
query = filterGasorderContractProductCandidates(query, contractID)
} else {
query = query.Order("gasorder_contract_product.created_at desc")
}
var total int64
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
var list []gasorderContractProductDisplay
if err := query.Offset((page - 1) * size).Limit(size).Scan(&list).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
respondProductDisplayList(ctx, total, list, &models.GasorderContractProduct{})
}
// GetGasorderContractProduct 返回单条合同气瓶绑定及当前智能气阀名称。
func GetGasorderContractProduct(ctx *gin.Context) {
var record gasorderContractProductDisplay
err := contractProductDisplayQuery(impl.DBService, "").
Where("gasorder_contract_product.identity = ?", ctx.Param("identity")).
Scan(&record).Error
if err != nil || record.ID == 0 {
if err == nil {
err = gorm.ErrRecordNotFound
}
common.RespondRecordError(ctx, err)
return
}
respondProductDisplay(ctx, record, &models.GasorderContractProduct{})
}
// ListGasorderItem 返回订单明细及当前智能气阀名称,编码和类型仍使用订单快照。
func ListGasorderItem(ctx *gin.Context) {
page, size := common.PageSize(ctx)
query := gasorderItemDisplayQuery(impl.DBService, ctx.Query("keyword")).Order("gasorder_item.created_at desc")
var total int64
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
var list []gasorderItemDisplay
if err := query.Offset((page - 1) * size).Limit(size).Scan(&list).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
respondProductDisplayList(ctx, total, list, &models.GasorderItem{})
}
// GetGasorderItem 返回单条订单明细及当前智能气阀名称。
func GetGasorderItem(ctx *gin.Context) {
var record gasorderItemDisplay
err := gasorderItemDisplayQuery(impl.DBService, "").
Where("gasorder_item.identity = ?", ctx.Param("identity")).Scan(&record).Error
if err != nil || record.ID == 0 {
if err == nil {
err = gorm.ErrRecordNotFound
}
common.RespondRecordError(ctx, err)
return
}
respondProductDisplay(ctx, record, &models.GasorderItem{})
}
// respondProductDisplayList 统一执行关系标识投影与位置保护后返回分页列表。
func respondProductDisplayList(ctx *gin.Context, total int64, value, model any) {
response, err := common.PublicResourceResponse(value)
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"total": total, "list": common.ProtectPreciseLocation(ctx, model, response)})
}
// respondProductDisplay 统一执行关系标识投影与位置保护后返回单条记录。
func respondProductDisplay(ctx *gin.Context, value, model any) {
response, err := common.PublicResourceResponse(value)
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, common.ProtectPreciseLocation(ctx, model, response))
}

View File

@@ -0,0 +1,69 @@
// 功能描述:验证合同气瓶和订单明细的设备名称派生、历史快照保留与组合搜索。
// 版本v1.0.0
package gasorder
import (
"strings"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// TestProductDisplayQueryUsesCurrentNameAndSnapshotFields 验证名称来自当前设备,类型和编码仍来自历史快照。
func TestProductDisplayQueryUsesCurrentNameAndSnapshotFields(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 records []gasorderContractProductDisplay
return contractProductDisplayQuery(tx, "阀门 A").Scan(&records)
})
for _, fragment := range []string{
"LEFT JOIN product_info AS display_product",
"AS product_name",
"display_product.name",
"gasorder_contract_product.product_code",
"gasorder_contract_product.product_type_name",
} {
if !strings.Contains(statement, fragment) {
t.Fatalf("设备显示查询缺少 %q%s", fragment, statement)
}
}
}
// TestGasorderItemDisplayQueryKeepsOrderSnapshot 验证订单详情同样只派生当前名称,不覆盖订单编码和类型快照。
func TestGasorderItemDisplayQueryKeepsOrderSnapshot(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 records []gasorderItemDisplay
return gasorderItemDisplayQuery(tx, "MOCK-CYLINDER").Scan(&records)
})
for _, fragment := range []string{
"gasorder_item.*",
"gasorder_item.product_code",
"gasorder_item.product_type_name",
"display_product.name",
} {
if !strings.Contains(statement, fragment) {
t.Fatalf("订单设备显示查询缺少 %q%s", fragment, statement)
}
}
}