Files
huachuang/docs/superpowers/plans/2026-08-17-sub-package-relation-batch-create.md
2026-08-18 18:44:13 +08:00

737 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 子卷包装关系批量新增实现计划
> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development推荐或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
**目标:** 为子卷包装关系提供可在卡片/表格间自由切换的批量新增弹窗,并由后端按“木箱码 + 木箱类型”分组校验容量、隔离保存和逐行返回失败原因。
**架构:** 前端批量弹窗以单一 `rows` 数组驱动两种视图,通过稳定 `clientKey` 映射后端错误;木箱类型复用现有选择弹窗,状态复用 `sub_package_status` 字典。后端新增独立批量 VO 和接口,由主 Service 完成逐行校验、分组和容量判断,再调用独立 `REQUIRES_NEW` 保存服务逐组写入,保证失败组不影响合法组。
**技术栈:** Java 17、Spring Boot、Jakarta Validation、MyBatis-Plus、JUnit 5、Mockito、Vue 3、TypeScript、Vben Modal、Antdv Next、Vitest。
---
## 文件结构
### 后端新增
- `nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo/SubPackageRelationBatchCreateReqVO.java`:批量请求容器。
- `nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo/SubPackageRelationBatchCreateItemReqVO.java`:单条批量录入数据。
- `nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo/SubPackageRelationBatchCreateRespVO.java`:成功/失败数量和失败行结构。
- `nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationBatchSaveService.java`:每个业务分组的独立事务写入器。
- `nl-module-lms/nl-module-lms-server/src/test/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationServiceImplTest.java`:分组、容量、部分成功和失败映射单元测试。
- `nl-module-lms/nl-module-lms-server/src/test/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationBatchSaveServiceTest.java`:分组批量写入委托测试。
### 后端修改
- `nl-module-lms/nl-module-lms-server/pom.xml`:补充 Mockito Inline 测试依赖。
- `nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/SubPackageRelationController.java`:暴露 `POST /batch-create`
- `nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationService.java`:声明批量创建方法。
- `nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationServiceImpl.java`:逐行校验、类型查询、分组、容量校验、逐组保存和响应组装。
- `nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/subpackagerelation/SubPackageRelationMapper.java`:统计相同木箱码和木箱类型已有记录。
- `nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/boxtype/BoxTypeMapper.java`:批量查询木箱类型。
### 前端新增
- `nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.ts`:批量行创建、复制、校验和失败结果归并的纯函数。
- `nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.test.ts`:纯函数单元测试。
- `nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form.vue`:批量新增弹窗、双视图和木箱类型选择组件接入。
### 前端修改
- `nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts`:增加 `SUB_PACKAGE_STATUS: 'sub_package_status'`
- `nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/lms/subpackagerelation/index.ts`:增加批量请求/响应类型和 API 方法。
- `nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/index.vue`:增加批量弹窗和工具栏入口。
## 任务 1定义后端批量契约和查询能力
**文件:**
- 创建:`nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo/SubPackageRelationBatchCreateReqVO.java`
- 创建:`nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo/SubPackageRelationBatchCreateItemReqVO.java`
- 创建:`nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo/SubPackageRelationBatchCreateRespVO.java`
- 修改:`nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/subpackagerelation/SubPackageRelationMapper.java`
- 修改:`nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/boxtype/BoxTypeMapper.java`
- [ ] **步骤 1创建独立批量请求实体**
请求容器只对列表本身做 Jakarta Validation行字段由 Service 手动校验,以便返回逐行错误而不是整次请求直接返回 400
```java
@Data
@Schema(description = "管理后台 - 子卷包装关系批量新增 Request VO")
public class SubPackageRelationBatchCreateReqVO {
@NotEmpty(message = "批量新增数据不能为空")
@Schema(description = "批量新增数据", requiredMode = Schema.RequiredMode.REQUIRED)
private List<SubPackageRelationBatchCreateItemReqVO> items;
}
```
单条实体字段固定为:
```java
@Data
@Schema(description = "管理后台 - 子卷包装关系批量新增单条数据")
public class SubPackageRelationBatchCreateItemReqVO {
private String clientKey;
private String packageBoxSn;
private String boxType;
private String boxLength;
private String boxWidth;
private String boxHigh;
private String qualityGuaranPeriod;
private String dateOfFgInbound;
private String containerName;
private String status;
}
```
- [ ] **步骤 2创建结构化批量响应实体**
```java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "管理后台 - 子卷包装关系批量新增 Response VO")
public class SubPackageRelationBatchCreateRespVO {
private Integer successCount;
private Integer failureCount;
private List<Failure> failures;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Failure {
private String clientKey;
private Integer rowIndex;
private String errorCode;
private String message;
}
}
```
- [ ] **步骤 3增加 Mapper 查询方法**
`BoxTypeMapper` 增加:
```java
default List<BoxTypeDO> selectListByBoxTypes(Collection<String> boxTypes) {
return selectList(BoxTypeDO::getBoxType, boxTypes);
}
```
`SubPackageRelationMapper` 增加:
```java
default Long selectCountByPackageBoxSnAndBoxType(String packageBoxSn, String boxType) {
return selectCount(new LambdaQueryWrapper<SubPackageRelationDO>()
.eq(SubPackageRelationDO::getPackageBoxSn, packageBoxSn)
.eq(SubPackageRelationDO::getBoxType, boxType));
}
```
- [ ] **步骤 4编译后端契约**
运行:
```powershell
mvn -pl nl-module-lms/nl-module-lms-server -am -DskipTests compile
```
预期:`BUILD SUCCESS`,新 VO 和 Mapper 默认方法编译通过。
- [ ] **步骤 5提交后端契约**
```powershell
git add nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/boxtype/BoxTypeMapper.java nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/subpackagerelation/SubPackageRelationMapper.java
git commit -m "feat: 定义子卷包装关系批量新增契约"
```
## 任务 2测试并实现分组事务写入器
**文件:**
- 修改:`nl-module-lms/nl-module-lms-server/pom.xml`
- 创建:`nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationBatchSaveService.java`
- 创建:`nl-module-lms/nl-module-lms-server/src/test/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationBatchSaveServiceTest.java`
- [ ] **步骤 1增加 Mockito Inline 测试依赖**
`pom.xml` 的测试依赖区增加:
```xml
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
```
- [ ] **步骤 2编写失败测试**
```java
@ExtendWith(MockitoExtension.class)
class SubPackageRelationBatchSaveServiceTest {
@Mock
private SubPackageRelationMapper subPackageRelationMapper;
@InjectMocks
private SubPackageRelationBatchSaveService service;
@Test
void shouldInsertWholeGroup() {
List<SubPackageRelationDO> group = List.of(
SubPackageRelationDO.builder().packageBoxSn("BX001").boxType("A").build(),
SubPackageRelationDO.builder().packageBoxSn("BX001").boxType("A").build());
service.saveGroup(group);
verify(subPackageRelationMapper).insertBatch(group);
}
}
```
- [ ] **步骤 3运行测试确认失败**
运行:
```powershell
mvn -pl nl-module-lms/nl-module-lms-server -Dtest=SubPackageRelationBatchSaveServiceTest test
```
预期FAIL提示 `SubPackageRelationBatchSaveService` 不存在。
- [ ] **步骤 4实现最小独立事务写入器**
```java
@Service
public class SubPackageRelationBatchSaveService {
@Resource
private SubPackageRelationMapper subPackageRelationMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void saveGroup(List<SubPackageRelationDO> group) {
subPackageRelationMapper.insertBatch(group);
}
}
```
- [ ] **步骤 5运行测试确认通过**
运行同一步骤 3。预期测试 PASS。
- [ ] **步骤 6提交事务写入器**
```powershell
git add nl-module-lms/nl-module-lms-server/pom.xml nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationBatchSaveService.java nl-module-lms/nl-module-lms-server/src/test/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationBatchSaveServiceTest.java
git commit -m "feat: 增加包装关系分组事务写入器"
```
## 任务 3测试并实现批量业务编排
**文件:**
- 创建:`nl-module-lms/nl-module-lms-server/src/test/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationServiceImplTest.java`
- 修改:`nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationService.java`
- 修改:`nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationServiceImpl.java`
- [ ] **步骤 1编写容量刚好满足和超限整组失败测试**
使用 `MockitoExtension` mock `SubPackageRelationMapper``BoxTypeMapper``SubPackageRelationBatchSaveService`。构造两组数据:
```java
private SubPackageRelationBatchCreateItemReqVO item(
String clientKey, String packageBoxSn, String boxType, String containerName) {
SubPackageRelationBatchCreateItemReqVO item = new SubPackageRelationBatchCreateItemReqVO();
item.setClientKey(clientKey);
item.setPackageBoxSn(packageBoxSn);
item.setBoxType(boxType);
item.setBoxLength("1200");
item.setBoxWidth("1000");
item.setBoxHigh("900");
item.setQualityGuaranPeriod("180");
item.setDateOfFgInbound("2026-08-17");
item.setContainerName(containerName);
item.setStatus("0");
return item;
}
```
核心断言:
```java
when(boxTypeMapper.selectListByBoxTypes(Set.of("A")))
.thenReturn(List.of(BoxTypeDO.builder().boxType("A").maxNum(3).build()));
when(subPackageRelationMapper.selectCountByPackageBoxSnAndBoxType("BX001", "A"))
.thenReturn(1L);
SubPackageRelationBatchCreateRespVO result = service.batchCreateSubPackageRelation(req(
item("k1", "BX001", "A", "R1"),
item("k2", "BX001", "A", "R2"))));
assertEquals(2, result.getSuccessCount());
assertEquals(0, result.getFailureCount());
verify(batchSaveService).saveGroup(argThat(group -> group.size() == 2));
```
将已有数量改为 `2L` 后断言:成功数为 0、失败数为 2、`errorCode``BOX_CAPACITY_EXCEEDED`、两个 `clientKey` 都返回,且 `batchSaveService` 未被调用。
- [ ] **步骤 2编写部分成功测试**
构造 `BX001/A` 超限、`BX002/A` 合法两个分组,断言:
```java
assertEquals(1, result.getSuccessCount());
assertEquals(2, result.getFailureCount());
assertEquals(Set.of("k1", "k2"), result.getFailures().stream()
.map(SubPackageRelationBatchCreateRespVO.Failure::getClientKey)
.collect(Collectors.toSet()));
verify(batchSaveService, times(1)).saveGroup(argThat(group ->
group.size() == 1 && "BX002".equals(group.get(0).getPackageBoxSn())));
```
- [ ] **步骤 3编写木箱类型不存在、必填失败和保存异常测试**
分别断言:
- 类型不存在返回 `BOX_TYPE_NOT_FOUND`,对应分组不保存。
-`packageBoxSn` 返回 `REQUIRED_FIELD_MISSING``rowIndex` 为请求中的一基行号。
- `batchSaveService.saveGroup` 对一个合法分组抛出异常时,该组返回 `GROUP_SAVE_FAILED`,后续合法组仍调用保存器并计入成功数。
- [ ] **步骤 4运行测试确认失败**
运行:
```powershell
mvn -pl nl-module-lms/nl-module-lms-server -Dtest=SubPackageRelationServiceImplTest test
```
预期FAIL提示批量方法和依赖尚不存在。
- [ ] **步骤 5声明 Service 方法并注入依赖**
`SubPackageRelationService` 增加:
```java
SubPackageRelationBatchCreateRespVO batchCreateSubPackageRelation(
SubPackageRelationBatchCreateReqVO reqVO);
```
`SubPackageRelationServiceImpl` 注入 `BoxTypeMapper``SubPackageRelationBatchSaveService`
- [ ] **步骤 6实现逐行校验、分组与容量判断**
实现顺序必须是:
1. 遍历请求项,按原始下标生成一基 `rowIndex`
2. 校验 `clientKey``packageBoxSn``boxType``qualityGuaranPeriod``dateOfFgInbound``containerName``status` 非空;失败项加入 `failures`,不参与分组。
3. 批量查询去重后的木箱类型并建立 `Map<String, BoxTypeDO>`
4. 按不可变分组键 `packageBoxSn + "\u0000" + boxType` 对合法项分组,同时保留每项的 `clientKey``rowIndex`
5. 类型不存在时整组追加 `BOX_TYPE_NOT_FOUND`
6. 查询相同木箱码和类型的数据库已有数量;当 `existing + group.size() > maxNum` 时整组追加 `BOX_CAPACITY_EXCEEDED`
7. 将合法项手动映射为 `SubPackageRelationDO`,只设置批量请求允许的业务字段。
不得使用 `BeanUtils.copyProperties`;使用显式 setter 或 builder 映射字段。
- [ ] **步骤 7实现逐组隔离保存和响应汇总**
逐个合法分组调用 `batchSaveService.saveGroup(group)`。成功时累加该组条数;捕获运行时异常时,为该组所有请求项追加 `GROUP_SAVE_FAILED`,然后继续处理下一组。
最终返回:
```java
return SubPackageRelationBatchCreateRespVO.builder()
.successCount(successCount)
.failureCount(failures.size())
.failures(failures)
.build();
```
- [ ] **步骤 8运行 Service 测试确认通过**
运行同一步骤 4。预期全部测试 PASS。
- [ ] **步骤 9提交批量业务逻辑**
```powershell
git add nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation nl-module-lms/nl-module-lms-server/src/test/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationServiceImplTest.java
git commit -m "feat: 实现包装关系分组批量保存"
```
## 任务 4暴露后端批量创建接口
**文件:**
- 修改:`nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/SubPackageRelationController.java`
- [ ] **步骤 1增加 Controller 方法**
```java
@PostMapping("/batch-create")
@Operation(summary = "批量创建子卷包装关系")
@PreAuthorize("@ss.hasPermission('lms:sub-package-relation:create')")
public CommonResult<SubPackageRelationBatchCreateRespVO> batchCreateSubPackageRelation(
@Valid @RequestBody SubPackageRelationBatchCreateReqVO reqVO) {
return success(subPackageRelationService.batchCreateSubPackageRelation(reqVO));
}
```
- [ ] **步骤 2运行 LMS Service 测试和编译**
```powershell
mvn -pl nl-module-lms/nl-module-lms-server -Dtest=SubPackageRelationServiceImplTest,SubPackageRelationBatchSaveServiceTest test
mvn -pl nl-module-lms/nl-module-lms-server -am -DskipTests compile
```
预期:测试全部 PASS编译 `BUILD SUCCESS`
- [ ] **步骤 3提交接口**
```powershell
git add nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/SubPackageRelationController.java
git commit -m "feat: 暴露包装关系批量新增接口"
```
## 任务 5定义前端字典、类型和 API
**文件:**
- 修改:`nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts`
- 修改:`nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/lms/subpackagerelation/index.ts`
- [ ] **步骤 1增加 LMS 状态字典常量**
`LMS_DICT` 中增加:
```ts
SUB_PACKAGE_STATUS: 'sub_package_status', // 子卷包装关系状态
```
- [ ] **步骤 2增加批量请求和响应类型**
```ts
export interface BatchCreateItem {
clientKey: string;
packageBoxSn: string;
boxType: string;
boxLength?: string;
boxWidth?: string;
boxHigh?: string;
qualityGuaranPeriod: string;
dateOfFgInbound: string;
containerName: string;
status: string;
}
export interface BatchCreateFailure {
clientKey: string;
rowIndex: number;
errorCode: string;
message: string;
}
export interface BatchCreateResult {
successCount: number;
failureCount: number;
failures: BatchCreateFailure[];
}
```
- [ ] **步骤 3增加批量 API 方法**
```ts
export function batchCreateSubPackageRelation(
items: LmsSubPackageRelationApi.BatchCreateItem[],
) {
return requestClient.post<LmsSubPackageRelationApi.BatchCreateResult>(
'/lms/sub-package-relation/batch-create',
{ items },
);
}
```
- [ ] **步骤 4运行目标应用类型检查并记录基线**
```powershell
pnpm -F @vben/web-antdv-next typecheck
```
预期PASS若现有工程基线报错保存完整输出后续验证只允许新增文件不增加错误。
- [ ] **步骤 5提交前端契约**
```powershell
git add nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/lms/subpackagerelation/index.ts
git commit -m "feat: 增加包装关系批量新增前端契约"
```
## 任务 6测试并实现批量行模型
**文件:**
- 创建:`nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.ts`
- 创建:`nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.test.ts`
- [ ] **步骤 1编写失败的纯函数测试**
测试必须覆盖:
```ts
describe('batch form model', () => {
it('creates a blank row with status 0 and a stable key', () => {
const row = createBatchRow();
expect(row.status).toBe('0');
expect(row.clientKey).toBeTruthy();
});
it('copies reusable values and clears unique identifiers', () => {
const copied = copyBatchRow(sourceRow);
expect(copied.boxType).toBe(sourceRow.boxType);
expect(copied.boxLength).toBe(sourceRow.boxLength);
expect(copied.packageBoxSn).toBe('');
expect(copied.containerName).toBe('');
expect(copied.clientKey).not.toBe(sourceRow.clientKey);
});
it('returns field errors for every incomplete row', () => {
const errors = validateBatchRows([createBatchRow()]);
expect(errors.get(0)).toEqual(expect.objectContaining({
packageBoxSn: expect.any(String),
boxType: expect.any(String),
containerName: expect.any(String),
}));
});
it('keeps only backend failure rows and maps messages by clientKey', () => {
const result = applyBatchResult(rows, {
successCount: 1,
failureCount: 1,
failures: [{ clientKey: rows[1]!.clientKey, rowIndex: 2, errorCode: 'BOX_CAPACITY_EXCEEDED', message: '容量超限' }],
});
expect(result.rows).toHaveLength(1);
expect(result.rows[0]!.clientKey).toBe(rows[1]!.clientKey);
expect(result.rowErrors.get(rows[1]!.clientKey)).toBe('容量超限');
});
});
```
- [ ] **步骤 2运行测试确认失败**
在前端仓库根目录运行:
```powershell
pnpm exec vitest run apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.test.ts
```
预期FAIL提示模型模块或导出函数不存在。
- [ ] **步骤 3实现最小批量行模型**
导出以下稳定类型和函数:
```ts
export interface BatchFormRow extends LmsSubPackageRelationApi.BatchCreateItem {}
export type BatchFieldErrors = Partial<Record<keyof BatchFormRow, string>>;
export function createBatchRow(): BatchFormRow;
export function copyBatchRow(source: BatchFormRow): BatchFormRow;
export function validateBatchRows(rows: BatchFormRow[]): Map<number, BatchFieldErrors>;
export function applyBatchResult(
rows: BatchFormRow[],
result: LmsSubPackageRelationApi.BatchCreateResult,
): { rowErrors: Map<string, string>; rows: BatchFormRow[] };
```
`clientKey` 使用 `crypto.randomUUID()`;如果运行环境不可用,则使用时间戳与自增序号组合的模块内 fallback。必填错误信息使用明确中文字段名。
- [ ] **步骤 4运行测试确认通过**
运行同一步骤 2。预期测试 PASS。
- [ ] **步骤 5提交批量行模型**
```powershell
git add nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.ts nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.test.ts
git commit -m "feat: 增加包装关系批量录入模型"
```
## 任务 7实现卡片/表格自由切换弹窗
**文件:**
- 创建:`nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form.vue`
- [ ] **步骤 1建立弹窗状态和木箱类型选择接入**
组件状态固定为:
```ts
const emit = defineEmits<{ success: [] }>();
const rows = ref<BatchFormRow[]>([createBatchRow()]);
const viewMode = ref<'card' | 'table'>('table');
const activeClientKey = ref<string>();
const fieldErrors = ref(new Map<number, BatchFieldErrors>());
const rowErrors = ref(new Map<string, string>());
const [BoxTypeSelectModal, boxTypeSelectModalApi] = useVbenModal({
connectedComponent: BoxTypeSelectModalComponent,
});
```
点击任一行木箱类型时设置 `activeClientKey` 并以 `{ multiple: false }` 打开选择弹窗;`select` 回调按 `clientKey` 精确回填 `boxType``boxLength``boxWidth``boxHigh`
- [ ] **步骤 2实现行操作和双视图共享数据**
新增、复制、删除、清空都只修改 `rows`。删除最后一条或清空时恢复 `[createBatchRow()]`。视图切换只修改 `viewMode`
卡片视图按记录渲染两栏表单;表格视图使用原生 `table` 或 Antdv Next `Table` 的横向可编辑布局。两种视图必须包含相同字段和相同 `v-model` 数据源,尺寸字段只读。
- [ ] **步骤 3接入状态字典和日期控件**
```ts
const statusOptions = getDictOptions(DICT_TYPE.SUB_PACKAGE_STATUS);
```
状态使用 `Select`,新记录默认 `'0'`。入库日期使用日期控件并在请求前格式化为 `YYYY-MM-DD`;不得把 Dayjs 对象传给后端。
- [ ] **步骤 4实现提交和部分成功处理**
确认按钮逻辑:
```ts
const validation = validateBatchRows(rows.value);
if (validation.size > 0) {
fieldErrors.value = validation;
message.warning('请检查必填项');
return;
}
const result = await batchCreateSubPackageRelation(rows.value);
const applied = applyBatchResult(rows.value, result);
if (result.successCount > 0) emit('success');
if (result.failureCount === 0) {
await modalApi.close();
message.success(`成功新增 ${result.successCount} 条子卷包装关系`);
return;
}
rows.value = applied.rows;
rowErrors.value = applied.rowErrors;
message.warning(`成功 ${result.successCount} 条,失败 ${result.failureCount} 条`);
```
`finally` 中解锁弹窗。关闭弹窗时重置所有状态,防止下次打开残留数据和错误。
- [ ] **步骤 5执行组件静态检查**
```powershell
pnpm exec eslint apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form.vue apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.ts
pnpm -F @vben/web-antdv-next typecheck
```
预期:无新增 ESLint 或 TypeScript 错误。
- [ ] **步骤 6提交批量弹窗**
```powershell
git add nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form.vue
git commit -m "feat: 实现包装关系批量新增弹窗"
```
## 任务 8接入列表入口并完成联调验证
**文件:**
- 修改:`nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/index.vue`
- [ ] **步骤 1接入批量弹窗**
```ts
import BatchForm from './modules/batch-form.vue';
const [BatchFormModal, batchFormModalApi] = useVbenModal({
connectedComponent: BatchForm,
destroyOnClose: true,
});
function handleBatchCreate() {
batchFormModalApi.open();
}
```
模板中增加:
```vue
<BatchFormModal @success="handleRefresh" />
```
- [ ] **步骤 2增加工具栏按钮**
在普通新增按钮后增加:
```ts
{
label: '批量新增',
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['lms:sub-package-relation:create'],
onClick: handleBatchCreate,
},
```
- [ ] **步骤 3运行后端定向测试和编译**
```powershell
mvn -pl nl-module-lms/nl-module-lms-server -Dtest=SubPackageRelationServiceImplTest,SubPackageRelationBatchSaveServiceTest test
mvn -pl nl-module-lms/nl-module-lms-server -am -DskipTests compile
```
预期:测试 PASS编译 `BUILD SUCCESS`
- [ ] **步骤 4运行前端测试和检查**
```powershell
pnpm exec vitest run apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.test.ts
pnpm exec eslint apps/web-antdv-next/src/views/lms/subpackagerelation/index.vue apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form.vue apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.ts
pnpm -F @vben/web-antdv-next typecheck
```
预期:测试 PASS目标文件静态检查通过类型检查无新增错误。
- [ ] **步骤 5手工联调验收**
按以下顺序检查:
1. 列表页能打开批量弹窗。
2. 新增两行,在表格输入后切到卡片,数据保持一致。
3. 选择木箱类型,确认回填目标行和尺寸。
4. 确认状态选项来自 `sub_package_status`,新增行默认 `0`
5. 提交一个合法组,弹窗关闭且列表刷新。
6. 提交一个超限组和一个合法组,合法组保存,超限组保留并逐行显示容量错误。
7. 修改失败行后再次提交,成功后弹窗关闭。
- [ ] **步骤 6提交列表接入**
```powershell
git add nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/index.vue
git commit -m "feat: 接入包装关系批量新增入口"
```
## 任务 9最终一致性检查
**文件:**
- 检查本计划列出的全部新增和修改文件。
- [ ] **步骤 1检查工作区变更范围**
```powershell
git status --short
git diff --check
git diff --stat
```
预期:没有空白错误;不修改用户已有的配置、原型和其他计划文件。
- [ ] **步骤 2核对规格覆盖**
逐项确认:独立接口、独立 VO、原型字段范围、木箱类型弹窗、尺寸回填、状态字典默认值、双视图共享数据、容量统计包含数据库已有数量、超限整组失败、合法组独立保存、逐行错误展示全部已实现。
- [ ] **步骤 3记录验证证据**
交付说明必须列出实际运行的每条命令、退出码和关键结果;如果检查被现有依赖或工具链阻塞,明确给出原始错误摘要,不得声称对应检查通过。