feat(t222): add freight ingestion admin flow
This commit is contained in:
@@ -71,6 +71,136 @@ func (h *Handler) RegisterProtected(routes gin.IRoutes) {
|
||||
SecurityHeaders(),
|
||||
h.AuthorizeOrder,
|
||||
)
|
||||
if _, ok := h.service.(FreightService); ok {
|
||||
routes.GET("/freight", SecurityHeaders(), h.ListFreight)
|
||||
routes.GET("/freight/import", SecurityHeaders(), h.ImportFreight)
|
||||
routes.POST("/freight/import", SecurityHeaders(), h.CreateFreightImport)
|
||||
routes.GET("/freight/:id", SecurityHeaders(), h.FreightDetail)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) ListFreight(ctx *gin.Context) {
|
||||
service := h.service.(FreightService)
|
||||
orders, err := service.ListFreightOrders(
|
||||
ctx.Request.Context(),
|
||||
defaultListLimit,
|
||||
)
|
||||
if err != nil {
|
||||
h.renderServiceError(ctx, err, "无法加载货运列表,请稍后重试。")
|
||||
return
|
||||
}
|
||||
token, err := csrfToken(ctx)
|
||||
if err != nil {
|
||||
h.renderError(ctx, http.StatusInternalServerError, "页面暂时无法打开", "请稍后重试。")
|
||||
return
|
||||
}
|
||||
h.render(ctx, http.StatusOK, "freight", freightPage{
|
||||
Page: pageView{
|
||||
Title: "ERP 货运",
|
||||
FreightCurrent: true,
|
||||
CSRFToken: token,
|
||||
},
|
||||
Orders: orders,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) ImportFreight(ctx *gin.Context) {
|
||||
service := h.service.(FreightService)
|
||||
token, err := csrfToken(ctx)
|
||||
if err != nil {
|
||||
h.renderError(ctx, http.StatusInternalServerError, "页面暂时无法打开", "请稍后重试。")
|
||||
return
|
||||
}
|
||||
key, err := newToken()
|
||||
if err != nil {
|
||||
h.renderError(ctx, http.StatusInternalServerError, "页面暂时无法打开", "请稍后重试。")
|
||||
return
|
||||
}
|
||||
page := freightImportPage{
|
||||
Page: pageView{
|
||||
Title: "导入 ERP 货运",
|
||||
FreightCurrent: true,
|
||||
CSRFToken: token,
|
||||
},
|
||||
IdempotencyKey: key,
|
||||
}
|
||||
if syncID := strings.TrimSpace(ctx.Query("sync")); syncID != "" {
|
||||
run, getErr := service.GetFreightSync(ctx.Request.Context(), syncID)
|
||||
if getErr == nil {
|
||||
page.Sync = &run
|
||||
}
|
||||
}
|
||||
h.render(ctx, http.StatusOK, "freight-import", page)
|
||||
}
|
||||
|
||||
func (h *Handler) CreateFreightImport(ctx *gin.Context) {
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 16<<10)
|
||||
if err := ctx.Request.ParseForm(); err != nil || !validCSRF(ctx) {
|
||||
h.renderError(ctx, http.StatusForbidden, "请求已失效", "请返回导入页面后重新提交。")
|
||||
return
|
||||
}
|
||||
orderNumber := strings.TrimSpace(ctx.PostForm("order_number"))
|
||||
key := strings.TrimSpace(ctx.PostForm("idempotency_key"))
|
||||
if orderNumber == "" || len([]byte(orderNumber)) > 128 ||
|
||||
!validToken(key) {
|
||||
token, _ := csrfToken(ctx)
|
||||
h.render(ctx, http.StatusUnprocessableEntity, "freight-import", freightImportPage{
|
||||
Page: pageView{
|
||||
Title: "导入 ERP 货运",
|
||||
FreightCurrent: true,
|
||||
CSRFToken: token,
|
||||
},
|
||||
OrderNumber: orderNumber,
|
||||
IdempotencyKey: key,
|
||||
Error: "请输入完整单号后重试。",
|
||||
})
|
||||
return
|
||||
}
|
||||
service := h.service.(FreightService)
|
||||
run, err := service.CreateFreightSync(
|
||||
ctx.Request.Context(),
|
||||
CreateFreightSyncInput{
|
||||
ActorUserID: actorUserID(ctx.Request.Context()),
|
||||
IdempotencyKey: key,
|
||||
OrderNumber: orderNumber,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
token, _ := csrfToken(ctx)
|
||||
h.render(ctx, serviceErrorStatus(err), "freight-import", freightImportPage{
|
||||
Page: pageView{
|
||||
Title: "导入 ERP 货运",
|
||||
FreightCurrent: true,
|
||||
CSRFToken: token,
|
||||
},
|
||||
OrderNumber: orderNumber,
|
||||
IdempotencyKey: key,
|
||||
Error: "同步任务创建失败,请稍后使用相同提交标识重试。",
|
||||
})
|
||||
return
|
||||
}
|
||||
ctx.Redirect(http.StatusSeeOther, "/freight/import?sync="+pathEscape(run.ID))
|
||||
}
|
||||
|
||||
func (h *Handler) FreightDetail(ctx *gin.Context) {
|
||||
service := h.service.(FreightService)
|
||||
detail, err := service.GetFreightOrder(
|
||||
ctx.Request.Context(),
|
||||
strings.TrimSpace(ctx.Param("id")),
|
||||
)
|
||||
if err != nil {
|
||||
h.renderServiceError(ctx, err, "无法加载货运详情,请稍后重试。")
|
||||
return
|
||||
}
|
||||
token, _ := csrfToken(ctx)
|
||||
h.render(ctx, http.StatusOK, "freight-detail", freightDetailPage{
|
||||
Page: pageView{
|
||||
Title: "货运详情",
|
||||
FreightCurrent: true,
|
||||
CSRFToken: token,
|
||||
},
|
||||
Detail: detail,
|
||||
})
|
||||
}
|
||||
|
||||
func SecurityHeaders() gin.HandlerFunc {
|
||||
@@ -739,10 +869,29 @@ func fallback(value string, fallbackValue string) string {
|
||||
}
|
||||
|
||||
type pageView struct {
|
||||
Title string
|
||||
TasksCurrent bool
|
||||
NewCurrent bool
|
||||
CSRFToken string
|
||||
Title string
|
||||
TasksCurrent bool
|
||||
NewCurrent bool
|
||||
FreightCurrent bool
|
||||
CSRFToken string
|
||||
}
|
||||
|
||||
type freightPage struct {
|
||||
Page pageView
|
||||
Orders []FreightOrder
|
||||
}
|
||||
|
||||
type freightImportPage struct {
|
||||
Page pageView
|
||||
OrderNumber string
|
||||
IdempotencyKey string
|
||||
Error string
|
||||
Sync *FreightSync
|
||||
}
|
||||
|
||||
type freightDetailPage struct {
|
||||
Page pageView
|
||||
Detail FreightOrderDetail
|
||||
}
|
||||
|
||||
type statusOption struct {
|
||||
|
||||
Reference in New Issue
Block a user