package service import ( "path/filepath" "strings" "testing" "github.com/xuri/excelize/v2" "cmautobuy/admin/model" "cmautobuy/admin/repository" ) func TestNormalizeInnerCodeSpecKey(t *testing.T) { tests := map[string]string{ "A-027 黑色,M碼 建議45-55公斤": "黑色,M", "杏色(预售), 2xl【备注】": "杏色,2XL", "白 色,均码": "白色,均码", "无逗号 规格": "无逗号规格", "": "", } for input, want := range tests { if got := NormalizeInnerCodeSpecKey(input); got != want { t.Errorf("NormalizeInnerCodeSpecKey(%q)=%q,期望 %q", input, got, want) } } } func TestParseInnerCodeWorkbook_表头前置行与重复业务组(t *testing.T) { book := excelize.NewFile() defaultSheet := book.GetSheetList()[0] if err := book.SetSheetName(defaultSheet, innerCodeSheetName); err != nil { t.Fatal(err) } rows := [][]string{ {"线下档口标签"}, {"标签打印序号", "内部档口入库码", "Shopee订单编号", "清洗后规格", "档口名称", "档口货号", "店铺名称", "生成日期"}, {"1", "DK001", "ORDER-1", "A-027 黑色,M碼 建議45-55公斤", "A档", "027", "shop-a", "2026-08-15"}, {"2", "DK001", "ORDER-1", "A-027 黑色,M碼 建議45-55公斤", "A档", "027", "shop-a", "2026-08-15"}, {"3", "DK002", "ORDER-2", "杏色,XL", "B档", "102", "shop-b", "2026-08-15"}, } setInnerCodeWorkbookRows(t, book, rows) result, imported, err := parseInnerCodeWorkbook(book, "labels.xlsx", "user-1") if err != nil { t.Fatal(err) } if result.TotalRows != 3 || result.MergedRows != 1 || len(imported) != 2 { t.Fatalf("统计不正确: result=%+v rows=%d", result, len(imported)) } first := imported[0] if result.BusinessDate != "2026-08-15" || first.BusinessDate != "2026-08-15" { t.Fatalf("生成日期解析不正确: result=%+v row=%+v", result, first) } if first.SpecKey != "黑色,M" || first.Stall != "A档#027" || first.SourceDuplicateCount != 2 { t.Fatalf("首行业务字段不正确: %+v", first) } if first.SourceRow != 3 || first.PrintSequence != 1 || first.ShopName != "shop-a" { t.Fatalf("首行来源字段不正确: %+v", first) } if first.InnerCode != "DK001" { t.Fatalf("相同入库码重复行不应重复拼接: %+v", first) } } func TestParseInnerCodeWorkbook_同日入库码指向不同业务键时报错(t *testing.T) { book := excelize.NewFile() defaultSheet := book.GetSheetList()[0] if err := book.SetSheetName(defaultSheet, innerCodeSheetName); err != nil { t.Fatal(err) } setInnerCodeWorkbookRows(t, book, [][]string{ {"内部档口入库码", "Shopee订单编号", "清洗后规格", "档口及货号", "生成日期"}, {"DK001", "ORDER-1", "黑色,M", "A#1", "2026-08-15"}, {"DK001", "ORDER-2", "白色,L", "B#2", "2026-08-15"}, }) _, _, err := parseInnerCodeWorkbook(book, "labels.xlsx", "user-1") if err == nil || !strings.Contains(err.Error(), "对应多组") { t.Fatalf("期望同日入库码冲突错误,实际 %v", err) } } func TestParseInnerCodeWorkbook_同业务键多个单件码按源行聚合(t *testing.T) { book := excelize.NewFile() defaultSheet := book.GetSheetList()[0] if err := book.SetSheetName(defaultSheet, innerCodeSheetName); err != nil { t.Fatal(err) } setInnerCodeWorkbookRows(t, book, [][]string{ {"内部档口入库码", "Shopee订单编号", "清洗后规格", "档口及货号", "生成日期", "商品件序"}, {"DK260815A160101", "26081394YBWB17", "黑色【原版好質量】,6XL建議127.5-150.0公斤", "山湖-易興网购#0035", "2026-08-15", "01"}, {"DK260815A160102", "26081394YBWB17", "黑色【原版好質量】,6XL建議127.5-150.0公斤", "山湖-易興网购#0035", "2026-08-15", "02"}, }) result, rows, err := parseInnerCodeWorkbook(book, "labels.xlsx", "user-1") if err != nil { t.Fatal(err) } if len(rows) != 1 || result.TotalRows != 2 || result.MergedRows != 1 { t.Fatalf("聚合统计不正确 result=%+v rows=%+v", result, rows) } if rows[0].InnerCode != "DK260815A160101,DK260815A160102" || rows[0].SourceDuplicateCount != 2 { t.Fatalf("多个单件码聚合不正确: %+v", rows[0]) } } func TestParseInnerCodeWorkbook_三个单件码稳定去重且超长时报错(t *testing.T) { book := newInnerCodeCodeGroupTestBook(t, []string{"DK003", "DK001", "DK003", "DK002"}) result, rows, err := parseInnerCodeWorkbook(book, "labels.xlsx", "user-1") book.Close() if err != nil { t.Fatal(err) } if len(rows) != 1 || rows[0].InnerCode != "DK003,DK001,DK002" || rows[0].SourceDuplicateCount != 4 || result.MergedRows != 3 { t.Fatalf("稳定去重结果不正确 result=%+v rows=%+v", result, rows) } longBook := newInnerCodeCodeGroupTestBook(t, []string{strings.Repeat("A", 70), strings.Repeat("B", 70)}) defer longBook.Close() _, _, err = parseInnerCodeWorkbook(longBook, "labels.xlsx", "user-1") if err == nil || !strings.Contains(err.Error(), "连接后超过 128 个字符") { t.Fatalf("期望聚合值超长错误,实际 %v", err) } } func TestParseInnerCodeWorkbook_单件码不能占用聚合分隔符(t *testing.T) { book := newInnerCodeCodeGroupTestBook(t, []string{"DK001,DK002"}) defer book.Close() _, _, err := parseInnerCodeWorkbook(book, "labels.xlsx", "user-1") if err == nil || !strings.Contains(err.Error(), "不能包含英文逗号") { t.Fatalf("期望逗号分隔符错误,实际 %v", err) } } func TestValidateExistingInnerCodeOwners_聚合中的单件码不能串到其他业务键(t *testing.T) { owners := []repository.InnerCodeCodeOwner{{ ID: 1, OrderNumber: "ORDER-1", Stall: "A#1", SpecKey: "黑色,M", InnerCode: "DK001,DK002", }} rows := []model.InnerCodeImportRow{{ BusinessDate: "2026-08-15", SourceRow: 8, OrderNumber: "ORDER-2", Stall: "B#2", SpecKey: "白色,L", InnerCode: "DK002,DK003", }} if err := validateExistingInnerCodeOwners("2026-08-15", owners, rows); err == nil || !strings.Contains(err.Error(), "已属于另一条记录") { t.Fatalf("期望组成码归属冲突,实际 %v", err) } rows[0].OrderNumber, rows[0].Stall, rows[0].SpecKey = "ORDER-1", "A#1", "黑色,M" if err := validateExistingInnerCodeOwners("2026-08-15", owners, rows); err != nil { t.Fatalf("同一业务键更新聚合值应允许,实际 %v", err) } } func TestValidateInnerCodeUpload(t *testing.T) { if err := ValidateInnerCodeUpload("labels.xlsx", 100, []byte{'P', 'K', 3, 4}); err != nil { t.Fatal(err) } for _, tc := range []struct { name string size int64 head []byte }{ {"labels.xls", 100, []byte{'P', 'K', 3, 4}}, {"labels.xlsx", 0, []byte{'P', 'K', 3, 4}}, {"labels.xlsx", MaxInnerCodeUploadBytes + 1, []byte{'P', 'K', 3, 4}}, {"labels.xlsx", 100, []byte("text")}, } { if err := ValidateInnerCodeUpload(tc.name, tc.size, tc.head); err == nil { t.Errorf("%+v 应被拒绝", tc) } } } func TestParseInnerCodeWorkbook_脱敏文件可重开(t *testing.T) { path := filepath.Join(t.TempDir(), "inner-code.xlsx") book := excelize.NewFile() defaultSheet := book.GetSheetList()[0] if err := book.SetSheetName(defaultSheet, innerCodeSheetName); err != nil { t.Fatal(err) } setInnerCodeWorkbookRows(t, book, [][]string{ {"内部档口入库码", "Shopee订单编号", "原始产品规格(源Excel D列,原样直拷)", "档口及货号", "生成日期"}, {"DK-TEST-1", "ORDER-TEST-1", "蓝色,L", "T#001", "08-15"}, }) if err := book.SaveAs(path); err != nil { t.Fatal(err) } book.Close() reopened, err := excelize.OpenFile(path) if err != nil { t.Fatal(err) } defer reopened.Close() _, imported, err := parseInnerCodeWorkbook(reopened, "labels_20260815_V3.3.xlsx", "user-1") if err != nil || len(imported) != 1 || imported[0].SpecKey != "蓝色,L" { t.Fatalf("重开解析失败 rows=%+v err=%v", imported, err) } } func TestParseInnerCodeWorkbook_短日期与文件名不一致时报错(t *testing.T) { book := newInnerCodeDateTestBook(t, "08-14") defer book.Close() _, _, err := parseInnerCodeWorkbook(book, "labels_20260815_V3.3.xlsx", "user-1") if err == nil || !strings.Contains(err.Error(), "与文件名日期") { t.Fatalf("期望文件名日期冲突,实际 %v", err) } } func TestParseInnerCodeWorkbook_混合生成日期整批失败(t *testing.T) { book := excelize.NewFile() defaultSheet := book.GetSheetList()[0] if err := book.SetSheetName(defaultSheet, innerCodeSheetName); err != nil { t.Fatal(err) } setInnerCodeWorkbookRows(t, book, [][]string{ {"内部档口入库码", "Shopee订单编号", "清洗后规格", "档口及货号", "生成日期"}, {"DK001", "ORDER-1", "黑色,M", "A#1", "2026-08-14"}, {"DK002", "ORDER-2", "白色,L", "B#2", "2026-08-15"}, }) defer book.Close() _, _, err := parseInnerCodeWorkbook(book, "labels.xlsx", "user-1") if err == nil || !strings.Contains(err.Error(), "多个生成日期") { t.Fatalf("期望混合日期错误,实际 %v", err) } } func TestParseInnerCodeWorkbook_缺少生成日期表头时报错(t *testing.T) { book := excelize.NewFile() defaultSheet := book.GetSheetList()[0] if err := book.SetSheetName(defaultSheet, innerCodeSheetName); err != nil { t.Fatal(err) } setInnerCodeWorkbookRows(t, book, [][]string{ {"内部档口入库码", "Shopee订单编号", "清洗后规格", "档口及货号"}, {"DK001", "ORDER-1", "黑色,M", "A#1"}, }) defer book.Close() _, _, err := parseInnerCodeWorkbook(book, "labels.xlsx", "user-1") if err == nil || !strings.Contains(err.Error(), "生成日期") { t.Fatalf("期望缺少生成日期表头错误,实际 %v", err) } } func newInnerCodeDateTestBook(t *testing.T, businessDate string) *excelize.File { t.Helper() book := excelize.NewFile() defaultSheet := book.GetSheetList()[0] if err := book.SetSheetName(defaultSheet, innerCodeSheetName); err != nil { t.Fatal(err) } setInnerCodeWorkbookRows(t, book, [][]string{ {"内部档口入库码", "Shopee订单编号", "清洗后规格", "档口及货号", "生成日期"}, {"DK001", "ORDER-1", "黑色,M", "A#1", businessDate}, }) return book } func newInnerCodeCodeGroupTestBook(t *testing.T, codes []string) *excelize.File { t.Helper() book := excelize.NewFile() defaultSheet := book.GetSheetList()[0] if err := book.SetSheetName(defaultSheet, innerCodeSheetName); err != nil { t.Fatal(err) } rows := [][]string{{"内部档口入库码", "Shopee订单编号", "清洗后规格", "档口及货号", "生成日期"}} for _, code := range codes { rows = append(rows, []string{code, "ORDER-1", "黑色,M", "A#1", "2026-08-15"}) } setInnerCodeWorkbookRows(t, book, rows) return book } func setInnerCodeWorkbookRows(t *testing.T, book *excelize.File, rows [][]string) { t.Helper() for rowIndex, row := range rows { for columnIndex, value := range row { cell, err := excelize.CoordinatesToCellName(columnIndex+1, rowIndex+1) if err != nil { t.Fatal(err) } if err := book.SetCellValue(innerCodeSheetName, cell, value); err != nil { t.Fatal(err) } } } }