优化合同气瓶设备名称显示
This commit is contained in:
@@ -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},
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
155
backend/api/internal/logic/platform/gasorder/product_display.go
Normal file
155
backend/api/internal/logic/platform/gasorder/product_display.go
Normal 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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user