完善配送点合同附件管理

This commit is contained in:
czl231
2026-08-23 01:08:25 +08:00
parent 4334719051
commit bebf9b52b7
17 changed files with 740 additions and 13 deletions

View File

@@ -1,3 +1,5 @@
// 功能描述:实现配送点范围内的合同、合同气瓶和配送订单接口。
// 版本v1.1.0。
package delivery
import (
@@ -7,6 +9,7 @@ import (
"fmt"
"io"
"math"
"strings"
"time"
"git.apinb.com/bsm-sdk/core/errcode"
@@ -48,10 +51,53 @@ func scopedContract(ctx *gin.Context, identity string, pointID uint64) (models.G
func ListContract(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if ok {
listScoped(ctx, &models.GasorderContract{}, common.ActiveRecords(db().Model(&models.GasorderContract{})).
Where("gas_basic_id = ? AND delivery_basic_id = ?", point.GasBasicID, point.ID), "gasorder_contract.created_at desc")
if !ok {
return
}
page, size := common.PageSize(ctx)
query := common.ApplyKeywordFilter(ctx,
common.ActiveRecords(db().Model(&models.GasorderContract{})).
Where("gas_basic_id = ? AND delivery_basic_id = ?", point.GasBasicID, point.ID),
&models.GasorderContract{})
var total int64
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
var list []models.GasorderContract
if err := query.Order("gasorder_contract.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, err := protectDeliveryContractListResponse(ctx, response, list)
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"total": total, "list": protected})
}
// protectDeliveryContractListResponse 删除内部附件路径,仅补充无敏感信息的存在性标识。
func protectDeliveryContractListResponse(ctx *gin.Context, response any, list []models.GasorderContract) ([]any, error) {
protected := common.ProtectPreciseLocation(ctx, &models.GasorderContract{}, response)
items, valid := protected.([]any)
if !valid || len(items) != len(list) {
return nil, errors.New("配送合同列表响应结构无效")
}
for index, item := range items {
row, rowValid := item.(map[string]any)
if !rowValid {
return nil, errors.New("配送合同列表记录结构无效")
}
// 只返回是否存在附件,内部存储 URI 已由统一保护层删除。
row["has_attachment"] = strings.TrimSpace(list[index].FileURI) != ""
}
return items, nil
}
func GetContract(ctx *gin.Context) {
@@ -63,6 +109,33 @@ func GetContract(ctx *gin.Context) {
}
}
// UploadContractAttachment 在当前配送点登录范围内上传受控 PDF 临时附件。
func UploadContractAttachment(ctx *gin.Context) {
if _, _, ok := currentScope(ctx); !ok {
return
}
platformgasorder.UploadGasorderContractAttachment(ctx)
}
// CleanupContractAttachment 清理当前配送点操作人尚未绑定的临时附件。
func CleanupContractAttachment(ctx *gin.Context) {
if _, _, ok := currentScope(ctx); !ok {
return
}
platformgasorder.CleanupGasorderContractAttachment(ctx)
}
// ServeContractAttachment 仅预览当前配送点拥有的正式合同附件。
func ServeContractAttachment(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
if _, valid := scopedContract(ctx, ctx.Param("identity"), point.ID); valid {
platformgasorder.ServeGasorderContractAttachment(ctx)
}
}
func CreateContract(ctx *gin.Context) {
point, station, ok := currentScope(ctx)
if !ok {

View File

@@ -0,0 +1,46 @@
// 功能描述:验证配送合同附件列表脱敏与公开状态投影。
// 版本v1.0.0。
package delivery
import (
"net/http/httptest"
"testing"
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/gin-gonic/gin"
)
// TestProtectDeliveryContractListResponse 验证内部路径不会进入配送端列表响应。
func TestProtectDeliveryContractListResponse(t *testing.T) {
gin.SetMode(gin.TestMode)
ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
ctx.Request = httptest.NewRequest("GET", "/gasorder_contract", nil)
contracts := []models.GasorderContract{
{FileURI: "/uploads/contracts/2026/08/22/protected.pdf"},
{},
}
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)
}
for index, item := range protected {
row, ok := item.(map[string]any)
if !ok {
t.Fatalf("第 %d 条响应不是对象", index)
}
if _, exposed := row["file_uri"]; exposed {
t.Fatalf("第 %d 条响应暴露了内部附件路径", index)
}
}
if protected[0].(map[string]any)["has_attachment"] != true {
t.Fatal("存在附件的合同必须返回 has_attachment=true")
}
if protected[1].(map[string]any)["has_attachment"] != false {
t.Fatal("无附件的合同必须返回 has_attachment=false")
}
}

View File

@@ -63,7 +63,10 @@ func RegisterDelivery(serviceKey string, engine *gin.Engine) {
contract := protected.Group("/gasorder_contract")
contract.GET("", deliverylogic.ListContract)
contract.POST("", deliverylogic.CreateContract)
contract.POST("/attachment/upload", deliverylogic.UploadContractAttachment)
contract.POST("/attachment/cleanup", deliverylogic.CleanupContractAttachment)
contract.GET("/:identity", deliverylogic.GetContract)
contract.GET("/:identity/attachment", deliverylogic.ServeContractAttachment)
contract.PUT("/:identity", deliverylogic.UpdateContract)
contract.POST("/:identity/activate", deliverylogic.ActivateContract)
contract.POST("/:identity/renew", deliverylogic.RenewContract)

View File

@@ -47,6 +47,9 @@ func TestDeliveryOrderActionBoundary(t *testing.T) {
"GET /heqi/delivery/v1/wallet_recharge/:identity",
"GET /heqi/delivery/v1/staff_account/:identity/avatar",
"GET /heqi/delivery/v1/user_account/:identity/avatar",
"POST /heqi/delivery/v1/gasorder_contract/attachment/upload",
"POST /heqi/delivery/v1/gasorder_contract/attachment/cleanup",
"GET /heqi/delivery/v1/gasorder_contract/:identity/attachment",
} {
if !routes[required] {
t.Fatalf("missing confirmed delivery action %s", required)