fix: 兼容相同顺运宝明细逐件回写 (#289)
This commit is contained in:
@@ -224,6 +224,7 @@ func planInnerCodeRowsWithReserved(records []model.InnerCodeRecord, stockIDsByOr
|
||||
plan.StockID, plan.DetailID = 0, 0
|
||||
plan.SybSpec, plan.SybSKU, plan.SybVariationSKU = "", "", ""
|
||||
plan.PurchasePlatform, plan.PurchaseCode, plan.RemoteInnerCode = "", "", ""
|
||||
plan.RemoteItemsJSON = ""
|
||||
if strings.TrimSpace(record.SpecRaw) == "" {
|
||||
plans = append(plans, innerCodeSkippedPlan(plan, "Excel 清洗后规格为空,跳过"))
|
||||
continue
|
||||
@@ -263,9 +264,48 @@ func planInnerCodeRowsWithReserved(records []model.InnerCodeRecord, stockIDsByOr
|
||||
plans = append(plans, innerCodeSkippedPlan(plan, message))
|
||||
continue
|
||||
}
|
||||
codes, codeErr := splitInnerCodes(record.InnerCode)
|
||||
if codeErr != nil || len(codes) != record.SourceDuplicateCount {
|
||||
plans = append(plans, innerCodeSkippedPlan(plan,
|
||||
fmt.Sprintf("单件入库码数量与 Excel 源行数不一致(入库码 %d 个,源行 %d 行)", len(codes), record.SourceDuplicateCount)))
|
||||
continue
|
||||
}
|
||||
specMatches := matchInnerCodeSpecItems(record.SpecRaw, eligible)
|
||||
sourceMatches := matchInnerCodeSourceSKUItems(record.SourceSKURaw, specMatches)
|
||||
matches, evidenceReason := matchInnerCodeItemsWithEvidence(record.Stall, record.SourceSKURaw, specMatches)
|
||||
if evidenceReason != "" {
|
||||
if evidenceReason == innerCodeSourceSKUDuplicateReason {
|
||||
items, primary, multipleErr := planExistingMatchedInnerCodeItems(record, codes, sourceMatches)
|
||||
if multipleErr == nil {
|
||||
raw, marshalErr := json.Marshal(items)
|
||||
if marshalErr != nil {
|
||||
plans = append(plans, innerCodeSkippedPlan(plan, "保存多明细逐件规划失败"))
|
||||
continue
|
||||
}
|
||||
for _, item := range items {
|
||||
used[record.OrderNumber][item.DetailID] = true
|
||||
}
|
||||
plan.StockID = stockID
|
||||
plan.DetailID = primary.ID
|
||||
plan.SybSpec = primary.ProductSpec
|
||||
plan.SybSKU = innerCodeRawText(primary.Raw["sku"])
|
||||
plan.SybVariationSKU = innerCodeRawText(primary.Raw["variationSku"])
|
||||
plan.RemoteInnerCode = innerCodeRawText(primary.Raw["innerExpCode"])
|
||||
plan.RemoteItemsJSON = string(raw)
|
||||
if allInnerCodeRemoteItemsConfirmed(items) {
|
||||
plan.Status = model.InnerCodeAlreadyFilled
|
||||
plan.ResultMessage = fmt.Sprintf("远端已存在全部 %d 个单件入库码,无需重复写入", len(codes))
|
||||
} else {
|
||||
plan.Status = model.InnerCodeReady
|
||||
plan.ResultMessage = "多条现成商品已逐件唯一匹配,等待操作员确认回写"
|
||||
}
|
||||
plans = append(plans, plan)
|
||||
continue
|
||||
}
|
||||
if multipleErr != nil {
|
||||
evidenceReason = multipleErr.Error()
|
||||
}
|
||||
}
|
||||
plans = append(plans, innerCodeSkippedPlan(plan, evidenceReason))
|
||||
continue
|
||||
}
|
||||
@@ -282,12 +322,6 @@ func planInnerCodeRowsWithReserved(records []model.InnerCodeRecord, stockIDsByOr
|
||||
continue
|
||||
}
|
||||
matched := matches[0]
|
||||
codes, codeErr := splitInnerCodes(record.InnerCode)
|
||||
if codeErr != nil || len(codes) != record.SourceDuplicateCount {
|
||||
plans = append(plans, innerCodeSkippedPlan(plan,
|
||||
fmt.Sprintf("单件入库码数量与 Excel 源行数不一致(入库码 %d 个,源行 %d 行)", len(codes), record.SourceDuplicateCount)))
|
||||
continue
|
||||
}
|
||||
if matched.ProductQty != len(codes) {
|
||||
plans = append(plans, innerCodeSkippedPlan(plan,
|
||||
fmt.Sprintf("顺运宝商品数量为 %d,单件入库码为 %d 个,数量不一致", matched.ProductQty, len(codes))))
|
||||
@@ -318,6 +352,96 @@ func planInnerCodeRowsWithReserved(records []model.InnerCodeRecord, stockIDsByOr
|
||||
return plans
|
||||
}
|
||||
|
||||
const (
|
||||
innerCodeSourceSKUDuplicateReason = "原始 SKU 候选重复,不能自动选择"
|
||||
innerCodeRemoteSourceExistingMatched = "existing_matched"
|
||||
)
|
||||
|
||||
// planExistingMatchedInnerCodeItems 只接受“多个单件码恰好对应多个现成数量 1 明细”的确定场景。
|
||||
// 返回项按 Excel 单件码顺序排列,明细候选使用稳定 ID 排序;已经存在的目标码优先保留原绑定。
|
||||
func planExistingMatchedInnerCodeItems(record model.InnerCodeRecord, codes []string, matches []syb.DetailItem) ([]innerCodeRemoteItem, syb.DetailItem, error) {
|
||||
if len(codes) < 2 || len(matches) != len(codes) {
|
||||
return nil, syb.DetailItem{}, fmt.Errorf("原始 SKU 命中 %d 条相同商品明细,但单件入库码为 %d 个,数量不一致", len(matches), len(codes))
|
||||
}
|
||||
candidates := append([]syb.DetailItem(nil), matches...)
|
||||
sort.Slice(candidates, func(i, j int) bool { return candidates[i].ID < candidates[j].ID })
|
||||
first := candidates[0]
|
||||
wantSpec := first.ProductSpec
|
||||
wantSKU := innerCodeRawText(first.Raw["sku"])
|
||||
wantVariationSKU := innerCodeRawText(first.Raw["variationSku"])
|
||||
codeIndex := make(map[string]int, len(codes))
|
||||
items := make([]innerCodeRemoteItem, len(codes))
|
||||
for index, code := range codes {
|
||||
codeIndex[code] = index
|
||||
items[index] = innerCodeRemoteItem{Code: code, Source: innerCodeRemoteSourceExistingMatched, Status: "planned"}
|
||||
}
|
||||
blank := make([]syb.DetailItem, 0, len(candidates))
|
||||
seenDetailIDs := make(map[int64]bool, len(candidates))
|
||||
for _, candidate := range candidates {
|
||||
if candidate.ID <= 0 || seenDetailIDs[candidate.ID] {
|
||||
return nil, syb.DetailItem{}, fmt.Errorf("重复候选包含无效或重复的商品明细 ID,不能自动逐件分配")
|
||||
}
|
||||
seenDetailIDs[candidate.ID] = true
|
||||
if candidate.ProductQty != 1 {
|
||||
return nil, syb.DetailItem{}, fmt.Errorf("相同候选商品明细 id=%d 的数量为 %d,不能按现成明细逐件分配", candidate.ID, candidate.ProductQty)
|
||||
}
|
||||
if candidate.ProductSpec != wantSpec || innerCodeRawText(candidate.Raw["sku"]) != wantSKU ||
|
||||
innerCodeRawText(candidate.Raw["variationSku"]) != wantVariationSKU {
|
||||
return nil, syb.DetailItem{}, fmt.Errorf("重复候选的规格或 SKU 身份不一致,不能自动逐件分配")
|
||||
}
|
||||
if strings.TrimSpace(record.Stall) != "" && !innerCodeStallMatches(record.Stall, candidate) {
|
||||
return nil, syb.DetailItem{}, fmt.Errorf("重复候选的档口及货号不一致,不能自动逐件分配")
|
||||
}
|
||||
if innerCodeRawText(candidate.Raw["purchasePlatform"]) != "" || innerCodeRawText(candidate.Raw["purchaseCode"]) != "" {
|
||||
return nil, syb.DetailItem{}, fmt.Errorf("重复候选中存在已有采购信息的商品,不能自动逐件分配")
|
||||
}
|
||||
remoteCode := innerCodeRawText(candidate.Raw["innerExpCode"])
|
||||
if remoteCode == "" {
|
||||
blank = append(blank, candidate)
|
||||
continue
|
||||
}
|
||||
index, ok := codeIndex[remoteCode]
|
||||
if !ok {
|
||||
return nil, syb.DetailItem{}, fmt.Errorf("重复候选中存在非目标快递单号,不能自动逐件分配")
|
||||
}
|
||||
if items[index].DetailID != 0 {
|
||||
return nil, syb.DetailItem{}, fmt.Errorf("单件入库码 %s 在重复候选中出现多次,不能自动逐件分配", remoteCode)
|
||||
}
|
||||
items[index].DetailID = candidate.ID
|
||||
items[index].Status = "confirmed"
|
||||
}
|
||||
blankIndex := 0
|
||||
for index := range items {
|
||||
if items[index].DetailID != 0 {
|
||||
continue
|
||||
}
|
||||
if blankIndex >= len(blank) {
|
||||
return nil, syb.DetailItem{}, fmt.Errorf("现成空白明细不足,不能完成逐件分配")
|
||||
}
|
||||
items[index].DetailID = blank[blankIndex].ID
|
||||
blankIndex++
|
||||
}
|
||||
if blankIndex != len(blank) {
|
||||
return nil, syb.DetailItem{}, fmt.Errorf("现成空白明细多于待写入单件码,不能自动逐件分配")
|
||||
}
|
||||
primaryID := items[0].DetailID
|
||||
for _, candidate := range candidates {
|
||||
if candidate.ID == primaryID {
|
||||
return items, candidate, nil
|
||||
}
|
||||
}
|
||||
return nil, syb.DetailItem{}, fmt.Errorf("多明细逐件规划缺少主商品明细")
|
||||
}
|
||||
|
||||
func allInnerCodeRemoteItemsConfirmed(items []innerCodeRemoteItem) bool {
|
||||
for _, item := range items {
|
||||
if item.Status != "confirmed" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(items) > 0
|
||||
}
|
||||
|
||||
func innerCodeSkippedPlan(plan model.InnerCodeRecord, message string) model.InnerCodeRecord {
|
||||
plan.Status = model.InnerCodeSkipped
|
||||
plan.ResultMessage = message
|
||||
@@ -350,17 +474,10 @@ func matchInnerCodeSpecItems(spec string, eligible []syb.DetailItem) []syb.Detai
|
||||
}
|
||||
|
||||
func matchInnerCodeItemsWithEvidence(stall, sourceSKURaw string, specMatches []syb.DetailItem) ([]syb.DetailItem, string) {
|
||||
sourceSKU := normalizeInnerCodeSourceSKU(sourceSKURaw)
|
||||
if sourceSKU != "" {
|
||||
sourceMatches := make([]syb.DetailItem, 0)
|
||||
for _, item := range specMatches {
|
||||
if normalizeInnerCodeSourceSKU(innerCodeRawText(item.Raw["sku"])) == sourceSKU ||
|
||||
normalizeInnerCodeSourceSKU(innerCodeRawText(item.Raw["variationSku"])) == sourceSKU {
|
||||
sourceMatches = append(sourceMatches, item)
|
||||
}
|
||||
}
|
||||
sourceMatches := matchInnerCodeSourceSKUItems(sourceSKURaw, specMatches)
|
||||
if normalizeInnerCodeSourceSKU(sourceSKURaw) != "" {
|
||||
if len(sourceMatches) > 1 {
|
||||
return nil, "原始 SKU 候选重复,不能自动选择"
|
||||
return nil, innerCodeSourceSKUDuplicateReason
|
||||
}
|
||||
if len(sourceMatches) == 1 {
|
||||
stallMatches := filterInnerCodeItemsByStallStrict(specMatches, stall)
|
||||
@@ -376,6 +493,21 @@ func matchInnerCodeItemsWithEvidence(stall, sourceSKURaw string, specMatches []s
|
||||
return filterInnerCodeItemsByStall(specMatches, stall), ""
|
||||
}
|
||||
|
||||
func matchInnerCodeSourceSKUItems(sourceSKURaw string, items []syb.DetailItem) []syb.DetailItem {
|
||||
sourceSKU := normalizeInnerCodeSourceSKU(sourceSKURaw)
|
||||
if sourceSKU == "" {
|
||||
return nil
|
||||
}
|
||||
matches := make([]syb.DetailItem, 0)
|
||||
for _, item := range items {
|
||||
if normalizeInnerCodeSourceSKU(innerCodeRawText(item.Raw["sku"])) == sourceSKU ||
|
||||
normalizeInnerCodeSourceSKU(innerCodeRawText(item.Raw["variationSku"])) == sourceSKU {
|
||||
matches = append(matches, item)
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
func filterInnerCodeItemsByStall(items []syb.DetailItem, stall string) []syb.DetailItem {
|
||||
stall = strings.TrimSpace(stall)
|
||||
if stall == "" {
|
||||
|
||||
Reference in New Issue
Block a user