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 | 否 | 规格 |
|
| spec | string | 否 | 规格 |
|
||||||
| manufacturer_model| string | 否 | 厂家型号 |
|
| manufacturer_model| string | 否 | 厂家型号 |
|
||||||
| base_unit_id | integer | 否 | 基本计量单位 ID |
|
| base_unit_id | integer | 否 | 基本计量单位 ID |
|
||||||
| unit | string | 否 | 单位名称(冗余展示字段) |
|
| unit | string | 否 | 单位编码(冗余展示字段) |
|
||||||
| material_image | string | 否 | 图片地址 |
|
| material_image | string | 否 | 图片地址 |
|
||||||
| organization_id | integer | 否 | 传入会被忽略,服务端取当前用户的组织 |
|
| organization_id | integer | 否 | 传入会被忽略,服务端取当前用户的组织 |
|
||||||
|
|
||||||
|
|
@ -306,7 +306,7 @@
|
||||||
| spec | string | 否 | 规格 |
|
| spec | string | 否 | 规格 |
|
||||||
| manufacturer_model| string | 否 | 厂家型号 |
|
| manufacturer_model| string | 否 | 厂家型号 |
|
||||||
| base_unit_id | integer | 否 | 基本计量单位 ID |
|
| base_unit_id | integer | 否 | 基本计量单位 ID |
|
||||||
| unit | string | 否 | 单位名称 |
|
| unit | string | 否 | 单位编码 |
|
||||||
| material_image | string | 否 | 图片地址 |
|
| material_image | string | 否 | 图片地址 |
|
||||||
| status | string | 否 | 物料状态 |
|
| status | string | 否 | 物料状态 |
|
||||||
|
|
||||||
|
|
@ -431,7 +431,7 @@
|
||||||
| 小类 | 必填,必须是该大类下已启用的小类,同样认名称、编码或「编码 名称」 |
|
| 小类 | 必填,必须是该大类下已启用的小类,同样认名称、编码或「编码 名称」 |
|
||||||
| 编码规则 | 该小类必须存在启用的编码规则(`code_rules.status = 'active'`) |
|
| 编码规则 | 该小类必须存在启用的编码规则(`code_rules.status = 'active'`) |
|
||||||
|
|
||||||
物料编码由系统按小类编码规则生成,**不接受模板里的编码列**。同一行可能产生多条原因,用中文分号连接。
|
物料编码由系统按小类编码规则生成,**不接受模板里的编码列**。单位落库统一写该单位的**编码**——模板里填名称(如「千克」)时也会转成编码,与单条新增保持一致。同一行可能产生多条原因,用中文分号连接。
|
||||||
|
|
||||||
**成功响应 `201 Created`:**
|
**成功响应 `201 Created`:**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@
|
||||||
| ---- | ------------------------------------------- |
|
| ---- | ------------------------------------------- |
|
||||||
| 物料名称 | 必填,去首尾空格后非空,≤ 200 字 |
|
| 物料名称 | 必填,去首尾空格后非空,≤ 200 字 |
|
||||||
| 规格 | 选填,≤ 200 字 |
|
| 规格 | 选填,≤ 200 字 |
|
||||||
| 单位 | 选填;为空时默认「个」。非空时必须在当前公司已启用单位中按名称或编码匹配到 |
|
| 单位 | 选填;为空时默认「个」。非空时必须在当前公司已启用单位中按名称或编码匹配到;落库统一写单位**编码**(与单条新增一致) |
|
||||||
| 大类 | 必填,必须是当前公司已启用的顶层分类:名称、编码或「编码 名称」写法,匹配前归一化空白 |
|
| 大类 | 必填,必须是当前公司已启用的顶层分类:名称、编码或「编码 名称」写法,匹配前归一化空白 |
|
||||||
| 小类 | 必填,必须是该大类下已启用的小类:名称、编码或「编码 名称」写法(按大类 ID + 小类写法组合定位) |
|
| 小类 | 必填,必须是该大类下已启用的小类:名称、编码或「编码 名称」写法(按大类 ID + 小类写法组合定位) |
|
||||||
| 编码规则 | 该小类必须存在 `status='active'` 的 `code_rules` 记录 |
|
| 编码规则 | 该小类必须存在 `status='active'` 的 `code_rules` 记录 |
|
||||||
|
|
@ -1349,6 +1349,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/DATA-DOG/go-sqlmock"
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
|
"github.com/wmms/wmms/internal/model"
|
||||||
"github.com/wmms/wmms/pkg/database"
|
"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: 跑测试确认失败**
|
- [ ] **Step 2: 跑测试确认失败**
|
||||||
|
|
@ -1947,13 +1991,16 @@ func (s *MaterialImportService) validateRows(organizationID int64, rows []model.
|
||||||
baseUnitID = &id
|
baseUnitID = &id
|
||||||
}
|
}
|
||||||
categoryID := minor.ID
|
categoryID := minor.ID
|
||||||
|
// 模板里填的是名称时也落编码:编码是单位表的唯一键,名称是可变的显示名,
|
||||||
|
// 物料表的 unit 只该有一种写法(与单条新增一致),否则库存页直接显示该
|
||||||
|
// 字段的地方会出现「千克」「kg」并存。
|
||||||
materials = append(materials, &materialImportItem{Material: &model.Material{
|
materials = append(materials, &materialImportItem{Material: &model.Material{
|
||||||
OrganizationID: organizationID,
|
OrganizationID: organizationID,
|
||||||
Name: name,
|
Name: name,
|
||||||
Spec: row.Spec,
|
Spec: row.Spec,
|
||||||
CategoryID: &categoryID,
|
CategoryID: &categoryID,
|
||||||
BaseUnitID: baseUnitID,
|
BaseUnitID: baseUnitID,
|
||||||
Unit: unitName,
|
Unit: unit.Code,
|
||||||
Status: "active",
|
Status: "active",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
}, categoryLabel: major.Name + " / " + minor.Name, unitLabel: unit.Name})
|
}, categoryLabel: major.Name + " / " + minor.Name, unitLabel: unit.Name})
|
||||||
|
|
|
||||||
|
|
@ -219,13 +219,16 @@ func (s *MaterialImportService) validateRows(organizationID int64, rows []model.
|
||||||
baseUnitID = &id
|
baseUnitID = &id
|
||||||
}
|
}
|
||||||
categoryID := minor.ID
|
categoryID := minor.ID
|
||||||
|
// 模板里填的是名称时也落编码:编码是单位表的唯一键,名称是可变的显示名,
|
||||||
|
// 物料表的 unit 只该有一种写法(与单条新增一致),否则库存页直接显示该
|
||||||
|
// 字段的地方会出现「千克」「kg」并存。
|
||||||
materials = append(materials, &materialImportItem{Material: &model.Material{
|
materials = append(materials, &materialImportItem{Material: &model.Material{
|
||||||
OrganizationID: organizationID,
|
OrganizationID: organizationID,
|
||||||
Name: name,
|
Name: name,
|
||||||
Spec: row.Spec,
|
Spec: row.Spec,
|
||||||
CategoryID: &categoryID,
|
CategoryID: &categoryID,
|
||||||
BaseUnitID: baseUnitID,
|
BaseUnitID: baseUnitID,
|
||||||
Unit: unitName,
|
Unit: unit.Code,
|
||||||
Status: "active",
|
Status: "active",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
}, categoryLabel: major.Name + " / " + minor.Name, unitLabel: unit.Name})
|
}, categoryLabel: major.Name + " / " + minor.Name, unitLabel: unit.Name})
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/DATA-DOG/go-sqlmock"
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
|
"github.com/wmms/wmms/internal/model"
|
||||||
"github.com/wmms/wmms/pkg/database"
|
"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