feat: 档口入库码后台批量回写 (#246)
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -53,21 +55,35 @@ func (h *Handler) InnerCodeList(c *gin.Context) {
|
||||
if keyword != "" {
|
||||
query.Set("q", keyword)
|
||||
}
|
||||
applyBatchID := strings.TrimSpace(c.Query("apply_batch_id"))
|
||||
applyProgress, err := service.GetInnerCodeApplyBatchProgress(h.db, applyBatchID)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, "读取后台回写进度失败,请稍后刷新重试。")
|
||||
return
|
||||
}
|
||||
if applyProgress != nil {
|
||||
query.Set("apply_batch_id", applyProgress.BatchID)
|
||||
}
|
||||
progressQuery := cloneURLValues(query)
|
||||
progressQuery.Set("page", strconv.Itoa(result.Page))
|
||||
progressQuery.Set("page_size", strconv.Itoa(pageSize))
|
||||
c.HTML(http.StatusOK, "inner_code/list", page(c, "inner-codes", "档口入库码", gin.H{
|
||||
"BusinessDate": businessDate,
|
||||
"StatusFilter": result.Status,
|
||||
"Keyword": keyword,
|
||||
"StatusOptions": service.InnerCodeStatusOptions(),
|
||||
"Rows": result.Rows,
|
||||
"HasAny": result.Counts.Total > 0,
|
||||
"IsFiltered": result.IsFiltered,
|
||||
"CurrentPage": result.Page,
|
||||
"CurrentPageSize": pageSize,
|
||||
"FeedbackMessage": feedbackMessage,
|
||||
"FeedbackSuccess": feedbackMessage != "" && feedbackKind == innerCodeFeedbackSuccess,
|
||||
"FeedbackError": feedbackMessage != "" && feedbackKind == innerCodeFeedbackError,
|
||||
"Status": service.InnerCodeStatusMessage(result),
|
||||
"Pagination": service.NewPaginationView(result.Page, pageSize, result.TotalPages, query.Encode()),
|
||||
"BusinessDate": businessDate,
|
||||
"StatusFilter": result.Status,
|
||||
"Keyword": keyword,
|
||||
"StatusOptions": service.InnerCodeStatusOptions(),
|
||||
"Rows": result.Rows,
|
||||
"HasAny": result.Counts.Total > 0,
|
||||
"IsFiltered": result.IsFiltered,
|
||||
"CurrentPage": result.Page,
|
||||
"CurrentPageSize": pageSize,
|
||||
"FeedbackMessage": feedbackMessage,
|
||||
"FeedbackSuccess": feedbackMessage != "" && feedbackKind == innerCodeFeedbackSuccess,
|
||||
"FeedbackError": feedbackMessage != "" && feedbackKind == innerCodeFeedbackError,
|
||||
"Status": service.InnerCodeStatusMessage(result),
|
||||
"ApplyProgress": applyProgress,
|
||||
"ApplyProgressURL": "/inner-codes?" + progressQuery.Encode(),
|
||||
"Pagination": service.NewPaginationView(result.Page, pageSize, result.TotalPages, query.Encode()),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -220,30 +236,60 @@ func ensureInnerCodeMatchSession(checkSession func() error, autoLogin func() (st
|
||||
return false, "顺运宝自动登录未完成:" + reason + ";没有执行匹配,请到“顺运宝数据”页面完成登录。"
|
||||
}
|
||||
|
||||
// InnerCodeApply 在操作员确认后逐条执行安全回写。
|
||||
// InnerCodeApply 在操作员确认后落库排队并立即返回,真实回写由单一后台执行器串行处理。
|
||||
func (h *Handler) InnerCodeApply(c *gin.Context) {
|
||||
businessDate, status, keyword := c.PostForm("date"), c.PostForm("status"), c.PostForm("q")
|
||||
pageNumber := service.ParsePage(c.PostForm("page"))
|
||||
ids := parseInnerCodeIDs(c.PostFormArray("ids"))
|
||||
client, message := h.innerCodeSybClient()
|
||||
_, message := h.innerCodeSybClient()
|
||||
if message != "" {
|
||||
h.innerCodeRedirect(c, businessDate, status, keyword, pageNumber, innerCodeFeedbackError, message)
|
||||
return
|
||||
}
|
||||
result, err := service.ApplyInnerCodes(c.Request.Context(), h.db, client, ids, currentUser(c).UserID)
|
||||
actorUserID := currentUser(c).UserID
|
||||
batch, err := service.QueueInnerCodeApplyBatch(h.db, ids, actorUserID)
|
||||
if err != nil {
|
||||
h.innerCodeRedirect(c, businessDate, status, keyword, pageNumber, innerCodeFeedbackError,
|
||||
"回写未完成:"+err.Error())
|
||||
"后台回写未提交:"+err.Error())
|
||||
return
|
||||
}
|
||||
message = fmt.Sprintf("回写完成:选择 %d 条,成功 %d 条,已存在 %d 条,跳过 %d 条,失败 %d 条,需核对 %d 条。",
|
||||
result.Requested, result.Updated, result.AlreadyFilled, result.Skipped, result.Failed, result.NeedsCheck)
|
||||
feedbackKind := innerCodeFeedbackSuccess
|
||||
if result.Failed > 0 || result.NeedsCheck > 0 {
|
||||
feedbackKind = innerCodeFeedbackError
|
||||
message += " 请查看失败或需核对的记录,确认远端结果后再继续。"
|
||||
}
|
||||
h.innerCodeRedirect(c, businessDate, status, keyword, pageNumber, feedbackKind, message)
|
||||
h.startInnerCodeApplyBatch(batch.ID, actorUserID)
|
||||
message = fmt.Sprintf("已提交 %d 条后台回写,页面可以继续使用;请刷新进度查看逐条结果。", batch.Count)
|
||||
h.innerCodeRedirectWithBatch(c, businessDate, status, keyword, pageNumber,
|
||||
innerCodeFeedbackSuccess, message, batch.ID)
|
||||
}
|
||||
|
||||
func (h *Handler) startInnerCodeApplyBatch(batchID, actorUserID string) {
|
||||
go func() {
|
||||
h.innerCodeApplyMu.Lock()
|
||||
defer h.innerCodeApplyMu.Unlock()
|
||||
var runErr error
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
runErr = fmt.Errorf("后台执行器异常:%v", recovered)
|
||||
}
|
||||
if runErr == nil {
|
||||
return
|
||||
}
|
||||
needsCheck, released, interruptErr := service.InterruptInnerCodeApplyBatch(h.db, batchID)
|
||||
if interruptErr != nil {
|
||||
log.Printf("档口入库码后台批次中断且状态收敛失败 batch_id=%s err=%v interrupt_err=%v", batchID, runErr, interruptErr)
|
||||
return
|
||||
}
|
||||
log.Printf("档口入库码后台批次中断 batch_id=%s needs_check=%d released=%d err=%v", batchID, needsCheck, released, runErr)
|
||||
}()
|
||||
client, message := h.innerCodeSybClient()
|
||||
if message != "" {
|
||||
runErr = errors.New(message)
|
||||
return
|
||||
}
|
||||
result, err := service.RunInnerCodeApplyBatch(context.Background(), h.db, client, batchID, actorUserID)
|
||||
runErr = err
|
||||
if err == nil {
|
||||
log.Printf("档口入库码后台批次完成 batch_id=%s processed=%d updated=%d already_filled=%d skipped=%d failed=%d needs_check=%d",
|
||||
batchID, result.Requested, result.Updated, result.AlreadyFilled, result.Skipped, result.Failed, result.NeedsCheck)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func parseInnerCodeIDs(rawIDs []string) []int64 {
|
||||
@@ -307,6 +353,10 @@ func (h *Handler) innerCodeSybClient() (*syb.Client, string) {
|
||||
}
|
||||
|
||||
func (h *Handler) innerCodeRedirect(c *gin.Context, businessDate, status, keyword string, pageNumber int, feedbackKind, message string) {
|
||||
h.innerCodeRedirectWithBatch(c, businessDate, status, keyword, pageNumber, feedbackKind, message, "")
|
||||
}
|
||||
|
||||
func (h *Handler) innerCodeRedirectWithBatch(c *gin.Context, businessDate, status, keyword string, pageNumber int, feedbackKind, message, batchID string) {
|
||||
values := url.Values{"date": {businessDate}, "page": {strconv.Itoa(pageNumber)}}
|
||||
if message = strings.TrimSpace(message); message != "" {
|
||||
values.Set("feedback", feedbackKind)
|
||||
@@ -319,5 +369,16 @@ func (h *Handler) innerCodeRedirect(c *gin.Context, businessDate, status, keywor
|
||||
if strings.TrimSpace(keyword) != "" {
|
||||
values.Set("q", strings.TrimSpace(keyword))
|
||||
}
|
||||
if strings.TrimSpace(batchID) != "" {
|
||||
values.Set("apply_batch_id", strings.TrimSpace(batchID))
|
||||
}
|
||||
c.Redirect(http.StatusSeeOther, "/inner-codes?"+values.Encode())
|
||||
}
|
||||
|
||||
func cloneURLValues(source url.Values) url.Values {
|
||||
result := make(url.Values, len(source))
|
||||
for key, values := range source {
|
||||
result[key] = append([]string(nil), values...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user