fix: 合并多件档口入库码回写 (#240)

This commit is contained in:
chengma
2026-08-15 14:27:31 +08:00
parent 5d72141f2f
commit 8e500606c5
10 changed files with 204 additions and 31 deletions
+63 -8
View File
@@ -21,6 +21,7 @@ import (
const (
MaxInnerCodeUploadBytes = 10 * 1024 * 1024
MaxInnerCodeImportRows = 5000
MaxInnerCodeValueRunes = 128
innerCodeSheetName = "标签入库码映射"
)
@@ -46,7 +47,7 @@ type InnerCodeImportResult struct {
CreatedCount int
UpdatedCount int
RestoredCount int
DuplicateRows int
MergedRows int
}
// IsInvalidInnerCodeImport 判断错误是否需要用户修正上传文件。
@@ -123,6 +124,13 @@ func ImportInnerCodeExcel(db *sql.DB, path, originalFilename, actorUserID string
return nil, fmt.Errorf("开始档口入库码导入事务失败: %w", err)
}
defer tx.Rollback()
owners, err := repository.LockInnerCodeCodeOwnersByDate(tx, result.BusinessDate)
if err != nil {
return nil, err
}
if err := validateExistingInnerCodeOwners(result.BusinessDate, owners, rows); err != nil {
return nil, fmt.Errorf("%w:%v", ErrInvalidInnerCodeImport, err)
}
now := model.NowISO()
for _, row := range rows {
outcome, err := repository.UpsertInnerCodeImportRow(tx, row, now)
@@ -156,6 +164,37 @@ type innerCodeParsedRow struct {
businessKey string
}
func innerCodeBusinessKey(businessDate, orderNumber, stall, specKey string) string {
return strings.Join([]string{businessDate, orderNumber, stall, specKey}, "\x00")
}
func validateExistingInnerCodeOwners(businessDate string, owners []repository.InnerCodeCodeOwner, rows []model.InnerCodeImportRow) error {
codeOwners := make(map[string]string)
for _, owner := range owners {
ownerKey := innerCodeBusinessKey(businessDate, owner.OrderNumber, owner.Stall, owner.SpecKey)
for _, code := range strings.Split(owner.InnerCode, ",") {
code = strings.TrimSpace(code)
if code == "" {
continue
}
if oldKey, exists := codeOwners[code]; exists && oldKey != ownerKey {
return fmt.Errorf("数据库中同日入库码 %q 已对应多条记录,请先人工核对", code)
}
codeOwners[code] = ownerKey
}
}
for _, row := range rows {
rowKey := innerCodeBusinessKey(row.BusinessDate, row.OrderNumber, row.Stall, row.SpecKey)
for _, code := range strings.Split(row.InnerCode, ",") {
if oldKey, exists := codeOwners[code]; exists && oldKey != rowKey {
return fmt.Errorf("Excel 第 %d 行的入库码 %q 在该业务日期已属于另一条记录", row.SourceRow, code)
}
codeOwners[code] = rowKey
}
}
return nil
}
func parseInnerCodeWorkbook(book *excelize.File, originalFilename, actorUserID string) (*InnerCodeImportResult, []model.InnerCodeImportRow, error) {
found := false
for _, name := range book.GetSheetList() {
@@ -242,7 +281,7 @@ func parseInnerCodeRows(rows *excelize.Rows, originalFilename, actorUserID strin
return nil, nil, err
}
specKey := NormalizeInnerCodeSpecKey(specRaw)
key := strings.Join([]string{businessDate, orderNumber, stall, specKey}, "\x00")
key := innerCodeBusinessKey(businessDate, orderNumber, stall, specKey)
parsed = append(parsed, innerCodeParsedRow{InnerCodeImportRow: model.InnerCodeImportRow{
BusinessDate: businessDate, SourceRow: rowNumber, PrintSequence: printSequence,
OrderNumber: orderNumber, ShopName: shop, Stall: stall, SpecRaw: specRaw,
@@ -278,15 +317,23 @@ func parseInnerCodeRows(rows *excelize.Rows, originalFilename, actorUserID strin
}
seen[original.businessKey] = true
group := groups[original.businessKey]
for _, duplicate := range group[1:] {
if duplicate.InnerCode != original.InnerCode {
return nil, nil, fmt.Errorf("第 %d 行与第 %d 行业务键相同但入库码不同", original.SourceRow, duplicate.SourceRow)
row := original.InnerCodeImportRow
codes := make([]string, 0, len(group))
seenCodes := make(map[string]bool, len(group))
for _, member := range group {
if !seenCodes[member.InnerCode] {
seenCodes[member.InnerCode] = true
codes = append(codes, member.InnerCode)
}
}
row := original.InnerCodeImportRow
row.InnerCode = strings.Join(codes, ",")
if utf8.RuneCountInString(row.InnerCode) > MaxInnerCodeValueRunes {
return nil, nil, fmt.Errorf("第 %d 行起的同业务键入库码连接后超过 %d 个字符(共 %d 个不同码)",
row.SourceRow, MaxInnerCodeValueRunes, len(codes))
}
row.SourceDuplicateCount = len(group)
unique = append(unique, row)
result.DuplicateRows += len(group) - 1
result.MergedRows += len(group) - 1
}
return result, unique, nil
}
@@ -377,12 +424,20 @@ func innerCodeCell(columns []string, index int) string {
}
func validateInnerCodeFields(row int, order, shop, stall, spec, code string) error {
if strings.Contains(code, ",") {
return fmt.Errorf("第 %d 行内部档口入库码不能包含英文逗号", row)
}
for _, character := range code {
if character < 32 || character == 127 {
return fmt.Errorf("第 %d 行内部档口入库码不能包含控制字符", row)
}
}
for _, field := range []struct {
name, value string
max int
}{
{"Shopee订单编号", order, 64}, {"店铺名称", shop, 191}, {"档口及货号", stall, 191},
{"规格", spec, 500}, {"内部档口入库码", code, 128},
{"规格", spec, 500}, {"内部档口入库码", code, MaxInnerCodeValueRunes},
} {
if utf8.RuneCountInString(field.value) > field.max {
return fmt.Errorf("第 %d 行%s超过 %d 个字符", row, field.name, field.max)