fix(material-import): 导入落库的单位统一写编码,与单条新增一致
模板里填单位名称(如「千克」)时原本把名称原样存进 materials.unit,而单条新增 经 syncUnit 提交的是单位编码 kg。同一个单位按录入路径留下两种写法,库存页直接 显示该字段(Stock.vue 的 unit 列)会出现「千克」与「kg」并存。 - material_import.go:Unit 一律取匹配到的单位 Code - 补回归用例:模板填名称、填编码两条路径都断言落库为 kg - 同步计划文档的代码与测试片段、设计约定 4 的单位一行 - 订正 api.md 中 unit 字段「单位名称」的错误描述(单条新增提交的本就是编码)
This commit is contained in:
parent
0ddda5363e
commit
8d195c2d51
|
|
@ -255,7 +255,7 @@
|
|||
| spec | string | 否 | 规格 |
|
||||
| manufacturer_model| string | 否 | 厂家型号 |
|
||||
| base_unit_id | integer | 否 | 基本计量单位 ID |
|
||||
| unit | string | 否 | 单位名称(冗余展示字段) |
|
||||
| unit | string | 否 | 单位编码(冗余展示字段) |
|
||||
| material_image | string | 否 | 图片地址 |
|
||||
| organization_id | integer | 否 | 传入会被忽略,服务端取当前用户的组织 |
|
||||
|
||||
|
|
@ -306,7 +306,7 @@
|
|||
| spec | string | 否 | 规格 |
|
||||
| manufacturer_model| string | 否 | 厂家型号 |
|
||||
| base_unit_id | integer | 否 | 基本计量单位 ID |
|
||||
| unit | string | 否 | 单位名称 |
|
||||
| unit | string | 否 | 单位编码 |
|
||||
| material_image | string | 否 | 图片地址 |
|
||||
| status | string | 否 | 物料状态 |
|
||||
|
||||
|
|
@ -431,7 +431,7 @@
|
|||
| 小类 | 必填,必须是该大类下已启用的小类,同样认名称、编码或「编码 名称」 |
|
||||
| 编码规则 | 该小类必须存在启用的编码规则(`code_rules.status = 'active'`) |
|
||||
|
||||
物料编码由系统按小类编码规则生成,**不接受模板里的编码列**。同一行可能产生多条原因,用中文分号连接。
|
||||
物料编码由系统按小类编码规则生成,**不接受模板里的编码列**。单位落库统一写该单位的**编码**——模板里填名称(如「千克」)时也会转成编码,与单条新增保持一致。同一行可能产生多条原因,用中文分号连接。
|
||||
|
||||
**成功响应 `201 Created`:**
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
| ---- | ------------------------------------------- |
|
||||
| 物料名称 | 必填,去首尾空格后非空,≤ 200 字 |
|
||||
| 规格 | 选填,≤ 200 字 |
|
||||
| 单位 | 选填;为空时默认「个」。非空时必须在当前公司已启用单位中按名称或编码匹配到 |
|
||||
| 单位 | 选填;为空时默认「个」。非空时必须在当前公司已启用单位中按名称或编码匹配到;落库统一写单位**编码**(与单条新增一致) |
|
||||
| 大类 | 必填,必须是当前公司已启用的顶层分类:名称、编码或「编码 名称」写法,匹配前归一化空白 |
|
||||
| 小类 | 必填,必须是该大类下已启用的小类:名称、编码或「编码 名称」写法(按大类 ID + 小类写法组合定位) |
|
||||
| 编码规则 | 该小类必须存在 `status='active'` 的 `code_rules` 记录 |
|
||||
|
|
@ -1349,6 +1349,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/wmms/wmms/internal/model"
|
||||
"github.com/wmms/wmms/pkg/database"
|
||||
)
|
||||
|
||||
|
|
@ -1714,6 +1715,49 @@ func TestMaterialImportServiceAcceptsWhitespaceVariantsInCategoryNames(t *testin
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 物料表的 unit 只存一种写法:单位编码。模板里填名称(界面显示的是「千克」这类
|
||||
// 显示名)时也得落成 kg,否则同一单位会按录入路径留下两种值——库存页直接显示该
|
||||
// 字段(Stock.vue 的 unit 列),会同时出现「千克」和「kg」。
|
||||
func TestMaterialImportServiceStoresUnitCodeWhenTemplateUsesUnitName(t *testing.T) {
|
||||
mock, cleanup := newImportServiceMock(t)
|
||||
defer cleanup()
|
||||
|
||||
mock.ExpectQuery(regexp.QuoteMeta(importCategoryQuery)).WithArgs(int64(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "organization_id", "code", "name", "description", "parent_id", "status"}).
|
||||
AddRow(3, 1, "A", "机械结构类", "", nil, "active").
|
||||
AddRow(12, 1, "A01", "标准件", "", 3, "active"))
|
||||
// 计量类单位:编码 kg、名称千克(计数类单位 code 与 name 相同,测不出差异)
|
||||
mock.ExpectQuery(regexp.QuoteMeta(importUnitQuery)).WithArgs(int64(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "organization_id", "code", "name", "precision_scale", "status"}).
|
||||
AddRow(7, 1, "kg", "千克", 3, "active"))
|
||||
mock.ExpectQuery(regexp.QuoteMeta(importRuleQuery)).WithArgs(int64(1), int64(12)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "organization_id", "category_id", "prefix", "sequence_length", "next_sequence", "status"}).
|
||||
AddRow(5, 1, 12, "A01", 4, 201, "active"))
|
||||
|
||||
rows := []model.MaterialImportRow{
|
||||
{Row: 2, Name: "内六角螺栓", Spec: "M8x30", Unit: "千克", MajorName: "机械结构类", MinorName: "标准件"},
|
||||
{Row: 3, Name: "垫圈", Spec: "M8", Unit: "kg", MajorName: "机械结构类", MinorName: "标准件"},
|
||||
}
|
||||
items, importErrors, err := NewMaterialImportService().validateRows(1, rows)
|
||||
if err != nil {
|
||||
t.Fatalf("validateRows: %v", err)
|
||||
}
|
||||
if len(importErrors) != 0 || len(items) != 2 {
|
||||
t.Fatalf("items = %d, importErrors = %+v", len(items), importErrors)
|
||||
}
|
||||
for i, item := range items {
|
||||
if item.Unit != "kg" {
|
||||
t.Fatalf("items[%d].Unit = %q, want 单位编码 %q", i, item.Unit, "kg")
|
||||
}
|
||||
}
|
||||
if items[0].BaseUnitID == nil || *items[0].BaseUnitID != 7 {
|
||||
t.Fatalf("baseUnitID = %v, want 7", items[0].BaseUnitID)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑测试确认失败**
|
||||
|
|
@ -1947,13 +1991,16 @@ func (s *MaterialImportService) validateRows(organizationID int64, rows []model.
|
|||
baseUnitID = &id
|
||||
}
|
||||
categoryID := minor.ID
|
||||
// 模板里填的是名称时也落编码:编码是单位表的唯一键,名称是可变的显示名,
|
||||
// 物料表的 unit 只该有一种写法(与单条新增一致),否则库存页直接显示该
|
||||
// 字段的地方会出现「千克」「kg」并存。
|
||||
materials = append(materials, &materialImportItem{Material: &model.Material{
|
||||
OrganizationID: organizationID,
|
||||
Name: name,
|
||||
Spec: row.Spec,
|
||||
CategoryID: &categoryID,
|
||||
BaseUnitID: baseUnitID,
|
||||
Unit: unitName,
|
||||
Unit: unit.Code,
|
||||
Status: "active",
|
||||
Version: 1,
|
||||
}, categoryLabel: major.Name + " / " + minor.Name, unitLabel: unit.Name})
|
||||
|
|
|
|||
|
|
@ -219,13 +219,16 @@ func (s *MaterialImportService) validateRows(organizationID int64, rows []model.
|
|||
baseUnitID = &id
|
||||
}
|
||||
categoryID := minor.ID
|
||||
// 模板里填的是名称时也落编码:编码是单位表的唯一键,名称是可变的显示名,
|
||||
// 物料表的 unit 只该有一种写法(与单条新增一致),否则库存页直接显示该
|
||||
// 字段的地方会出现「千克」「kg」并存。
|
||||
materials = append(materials, &materialImportItem{Material: &model.Material{
|
||||
OrganizationID: organizationID,
|
||||
Name: name,
|
||||
Spec: row.Spec,
|
||||
CategoryID: &categoryID,
|
||||
BaseUnitID: baseUnitID,
|
||||
Unit: unitName,
|
||||
Unit: unit.Code,
|
||||
Status: "active",
|
||||
Version: 1,
|
||||
}, categoryLabel: major.Name + " / " + minor.Name, unitLabel: unit.Name})
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/wmms/wmms/internal/model"
|
||||
"github.com/wmms/wmms/pkg/database"
|
||||
)
|
||||
|
||||
|
|
@ -375,3 +376,46 @@ func TestMaterialImportServiceAcceptsWhitespaceVariantsInCategoryNames(t *testin
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 物料表的 unit 只存一种写法:单位编码。模板里填名称(界面显示的是「千克」这类
|
||||
// 显示名)时也得落成 kg,否则同一单位会按录入路径留下两种值——库存页直接显示该
|
||||
// 字段(Stock.vue 的 unit 列),会同时出现「千克」和「kg」。
|
||||
func TestMaterialImportServiceStoresUnitCodeWhenTemplateUsesUnitName(t *testing.T) {
|
||||
mock, cleanup := newImportServiceMock(t)
|
||||
defer cleanup()
|
||||
|
||||
mock.ExpectQuery(regexp.QuoteMeta(importCategoryQuery)).WithArgs(int64(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "organization_id", "code", "name", "description", "parent_id", "status"}).
|
||||
AddRow(3, 1, "A", "机械结构类", "", nil, "active").
|
||||
AddRow(12, 1, "A01", "标准件", "", 3, "active"))
|
||||
// 计量类单位:编码 kg、名称千克(计数类单位 code 与 name 相同,测不出差异)
|
||||
mock.ExpectQuery(regexp.QuoteMeta(importUnitQuery)).WithArgs(int64(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "organization_id", "code", "name", "precision_scale", "status"}).
|
||||
AddRow(7, 1, "kg", "千克", 3, "active"))
|
||||
mock.ExpectQuery(regexp.QuoteMeta(importRuleQuery)).WithArgs(int64(1), int64(12)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "organization_id", "category_id", "prefix", "sequence_length", "next_sequence", "status"}).
|
||||
AddRow(5, 1, 12, "A01", 4, 201, "active"))
|
||||
|
||||
rows := []model.MaterialImportRow{
|
||||
{Row: 2, Name: "内六角螺栓", Spec: "M8x30", Unit: "千克", MajorName: "机械结构类", MinorName: "标准件"},
|
||||
{Row: 3, Name: "垫圈", Spec: "M8", Unit: "kg", MajorName: "机械结构类", MinorName: "标准件"},
|
||||
}
|
||||
items, importErrors, err := NewMaterialImportService().validateRows(1, rows)
|
||||
if err != nil {
|
||||
t.Fatalf("validateRows: %v", err)
|
||||
}
|
||||
if len(importErrors) != 0 || len(items) != 2 {
|
||||
t.Fatalf("items = %d, importErrors = %+v", len(items), importErrors)
|
||||
}
|
||||
for i, item := range items {
|
||||
if item.Unit != "kg" {
|
||||
t.Fatalf("items[%d].Unit = %q, want 单位编码 %q", i, item.Unit, "kg")
|
||||
}
|
||||
}
|
||||
if items[0].BaseUnitID == nil || *items[0].BaseUnitID != 7 {
|
||||
t.Fatalf("baseUnitID = %v, want 7", items[0].BaseUnitID)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user