feat: 新增检测单关联、基础数据优化与入库单升级
1. 新增出库单检测单号字段并添加索引支持溯源 2. 优化基础数据页面物料类型展示与选择逻辑 3. 升级入库单导入功能,支持自动生成单号并返回导入结果 4. 优化接驳台查询排序方式 5. 修复字符串拼接空格问题
This commit is contained in:
@@ -13,15 +13,102 @@ import (
|
||||
"bj_power_wms/ent/inspectionrecord"
|
||||
"bj_power_wms/ent/inventory"
|
||||
"bj_power_wms/ent/material"
|
||||
"bj_power_wms/ent/outboundorder"
|
||||
"bj_power_wms/internal/svc"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
// 物料品类:4=其他(辅料/工装/试验设备),与 schema/material.go 的 item_type 保持一致
|
||||
const invItemOther = 4
|
||||
|
||||
// nextDailyNo 生成日期+流水号,格式:IB/OB + YYYYMMDD + -NNN(如 IB20260921-001)。
|
||||
// 同一天内流水号递增,跨天重置为 001。并发冲突时自动重试最多 5 次。
|
||||
// 注意:历史旧单号(IB+UUID 片段)不受影响,仅新生成的用新格式。
|
||||
func nextDailyNo(ctx *svc.ServiceContext, prefix string) (string, error) {
|
||||
const maxRetry = 5
|
||||
var lastErr error
|
||||
for attempt := 0; attempt < maxRetry; attempt++ {
|
||||
no, err := genDailyNoOnce(ctx, prefix)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
time.Sleep(20 * time.Millisecond * time.Duration(attempt+1))
|
||||
continue
|
||||
}
|
||||
// 校验唯一性——如果刚好撞号,重试
|
||||
if prefix == "IB" {
|
||||
if _, e := ctx.EntClient.InboundOrder.Query().Where(inboundorder.InboundNoEQ(no)).Only(ctx0()); e == nil {
|
||||
lastErr = fmt.Errorf("单号 %s 已存在,重试中", no)
|
||||
time.Sleep(20 * time.Millisecond * time.Duration(attempt+1))
|
||||
continue
|
||||
}
|
||||
} else if prefix == "OB" {
|
||||
if _, e := ctx.EntClient.OutboundOrder.Query().Where(outboundorder.OutboundNoEQ(no)).Only(ctx0()); e == nil {
|
||||
lastErr = fmt.Errorf("单号 %s 已存在,重试中", no)
|
||||
time.Sleep(20 * time.Millisecond * time.Duration(attempt+1))
|
||||
continue
|
||||
}
|
||||
}
|
||||
return no, nil
|
||||
}
|
||||
if lastErr != nil {
|
||||
return "", fmt.Errorf("生成单号失败(重试 %d 次仍冲突): %w", maxRetry, lastErr)
|
||||
}
|
||||
return "", fmt.Errorf("生成单号失败(未知错误)")
|
||||
}
|
||||
|
||||
func genDailyNoOnce(ctx *svc.ServiceContext, prefix string) (string, error) {
|
||||
now := time.Now()
|
||||
datePart := now.Format("20060102")
|
||||
prefixDate := prefix + datePart + "-"
|
||||
|
||||
var maxSeq int
|
||||
var rows []string
|
||||
var err error
|
||||
if prefix == "IB" {
|
||||
rows, err = ctx.EntClient.InboundOrder.Query().
|
||||
Where(inboundorder.InboundNoHasPrefix(prefixDate)).
|
||||
Select(inboundorder.FieldInboundNo).
|
||||
Strings(ctx0())
|
||||
} else if prefix == "OB" {
|
||||
rows, err = ctx.EntClient.OutboundOrder.Query().
|
||||
Where(outboundorder.OutboundNoHasPrefix(prefixDate)).
|
||||
Select(outboundorder.FieldOutboundNo).
|
||||
Strings(ctx0())
|
||||
} else {
|
||||
return "", fmt.Errorf("未知前缀: %s", prefix)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, n := range rows {
|
||||
suffix := strings.TrimPrefix(n, prefixDate)
|
||||
if s, e := strconv.Atoi(suffix); e == nil && s > maxSeq {
|
||||
maxSeq = s
|
||||
}
|
||||
}
|
||||
seq := maxSeq + 1
|
||||
return fmt.Sprintf("%s%03d", prefixDate, seq), nil
|
||||
}
|
||||
|
||||
// validateUniqueNo 校验单据号唯一(InboundNo 或 OutboundNo 不能重复)。
|
||||
func validateUniqueNo(ctx *svc.ServiceContext, kind, no string) error {
|
||||
if no == "" {
|
||||
return nil // 空值表示走自动生成
|
||||
}
|
||||
switch kind {
|
||||
case "IB":
|
||||
if _, e := ctx.EntClient.InboundOrder.Query().Where(inboundorder.InboundNoEQ(no)).Only(ctx0()); e == nil {
|
||||
return fmt.Errorf("入库单号 %s 已存在", no)
|
||||
}
|
||||
case "OB":
|
||||
if _, e := ctx.EntClient.OutboundOrder.Query().Where(outboundorder.OutboundNoEQ(no)).Only(ctx0()); e == nil {
|
||||
return fmt.Errorf("出库单号 %s 已存在", no)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateInboundQuality 入库质量门禁:该物料/批次/SN 若已被判定"不合格",拒绝入库。
|
||||
// 依据客户诉求《问题记录》L135:「不合格不入库、不上产线,仅合格品入库存数量」。
|
||||
func validateInboundQuality(ctx *svc.ServiceContext, materialCode, batchNo string, snList []string) error {
|
||||
@@ -58,6 +145,7 @@ func validateInboundQuality(ctx *svc.ServiceContext, materialCode, batchNo strin
|
||||
func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
InboundNo string `json:"inboundNo"` // 可选:留空自动生成;填了必须唯一(如 IB20260921-001)
|
||||
InboundType string `json:"inboundType"` // purchase/semi/finished/return/other
|
||||
MaterialCode string `json:"materialCode"`
|
||||
BatchNo string `json:"batchNo"`
|
||||
@@ -117,7 +205,20 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
inboundNo := "IB" + uuid.NewString()[:12]
|
||||
inboundNo := strings.TrimSpace(req.InboundNo)
|
||||
if inboundNo != "" {
|
||||
if err := validateUniqueNo(ctx, "IB", inboundNo); err != nil {
|
||||
fail(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
no, err := nextDailyNo(ctx, "IB")
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, "生成入库单号失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
inboundNo = no
|
||||
}
|
||||
|
||||
// 质量状态自动带出(客户诉求 问题记录 L129:入库记录增加 合格/不合格 列)
|
||||
// 按检测单号查最近一次检验结论:合格→合格;不合格且部分入库→部分;不合格→不合格;无记录→空(未关联)
|
||||
@@ -348,6 +449,9 @@ func applyInboundFilters(q *ent.InboundOrderQuery, r *http.Request) *ent.Inbound
|
||||
// 2026-09-08 用户明确:禁止"关键字"多字段混搜,各字段独立筛选
|
||||
q = q.Where(inboundorder.MaterialNameContainsFold(v))
|
||||
}
|
||||
if v := r.URL.Query().Get("inspectionNo"); v != "" {
|
||||
q = q.Where(inboundorder.InspectionNoContainsFold(v))
|
||||
}
|
||||
// 单据状态:有效 / 已作废(作废单保留可查,但不参与库存)
|
||||
if v := r.URL.Query().Get("voided"); v == "true" {
|
||||
q = q.Where(inboundorder.VoidedEQ(true))
|
||||
|
||||
Reference in New Issue
Block a user