feat: 增加包装关系批量录入模型

This commit is contained in:
2026-08-17 21:26:10 +08:00
parent a07922a3d3
commit 9e2c5d04d3
4 changed files with 263 additions and 0 deletions

View File

@@ -3,6 +3,35 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace LmsSubPackageRelationApi {
/** 批量新增子卷包装关系项 */
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[];
}
/** 子卷包装关系信息 */
export interface SubPackageRelation {
id: number; // 子卷包装标识
@@ -55,6 +84,16 @@ export namespace LmsSubPackageRelationApi {
}
}
/** 批量新增子卷包装关系 */
export function batchCreateSubPackageRelation(
items: LmsSubPackageRelationApi.BatchCreateItem[],
) {
return requestClient.post<LmsSubPackageRelationApi.BatchCreateResult>(
'/lms/sub-package-relation/batch-create',
{ items },
);
}
/** 查询子卷包装关系分页 */
export function getSubPackageRelationPage(params: PageParam) {
return requestClient.get<PageResult<LmsSubPackageRelationApi.SubPackageRelation>>(

View File

@@ -0,0 +1,129 @@
import { describe, expect, it } from 'vitest';
import {
applyBatchResult,
copyBatchRow,
createBatchRow,
validateBatchRows,
} from './batch-form-model';
describe('batch-form-model', () => {
it('creates an empty row with default status and a unique client key', () => {
const first = createBatchRow();
const second = createBatchRow();
expect(first).toEqual({
clientKey: expect.any(String),
packageBoxSn: '',
boxType: '',
boxLength: '',
boxWidth: '',
boxHigh: '',
qualityGuaranPeriod: '',
dateOfFgInbound: '',
containerName: '',
status: '0',
});
expect(first.clientKey).not.toBe('');
expect(second.clientKey).not.toBe(first.clientKey);
});
it('copies reusable fields while clearing identifiers and runtime state', () => {
const source = Object.assign(createBatchRow(), {
packageBoxSn: 'BOX-001',
boxType: 'TYPE-A',
boxLength: '100',
boxWidth: '80',
boxHigh: '60',
qualityGuaranPeriod: '2027-01-01',
dateOfFgInbound: '2026-01-01',
containerName: 'ROLL-001',
status: '1',
rowError: '运行时错误',
});
const copied = copyBatchRow(source);
expect(copied).toMatchObject({
packageBoxSn: '',
boxType: 'TYPE-A',
boxLength: '100',
boxWidth: '80',
boxHigh: '60',
qualityGuaranPeriod: '2027-01-01',
dateOfFgInbound: '2026-01-01',
containerName: '',
status: '1',
});
expect(copied.clientKey).not.toBe(source.clientKey);
expect(copied).not.toHaveProperty('rowError');
});
it('reports all required fields when values are empty or whitespace', () => {
const row = createBatchRow();
row.packageBoxSn = ' ';
row.status = '\t';
expect(validateBatchRows([row]).get(0)).toEqual({
packageBoxSn: '木箱唯一码不能为空',
boxType: '木箱类型不能为空',
qualityGuaranPeriod: '保质期不能为空',
dateOfFgInbound: '入库日期不能为空',
containerName: '子卷号不能为空',
status: '状态不能为空',
});
});
it('does not report an error for a complete row', () => {
const row = Object.assign(createBatchRow(), {
packageBoxSn: 'BOX-001',
boxType: 'TYPE-A',
qualityGuaranPeriod: '2027-01-01',
dateOfFgInbound: '2026-01-01',
containerName: 'ROLL-001',
status: '0',
});
expect(validateBatchRows([row])).toEqual(new Map());
});
it('keeps failed rows in original order and maps server messages by client key', () => {
const rows = [createBatchRow(), createBatchRow(), createBatchRow()];
const result = applyBatchResult(rows, {
successCount: 1,
failureCount: 2,
failures: [
{
clientKey: rows[2]!.clientKey,
rowIndex: 2,
errorCode: 'INVALID',
message: '第三行失败',
},
{
clientKey: rows[0]!.clientKey,
rowIndex: 0,
errorCode: 'DUPLICATE',
message: '第一行失败',
},
],
});
expect(result.rows).toEqual([rows[0], rows[2]]);
expect(result.rowErrors).toEqual(
new Map([
[rows[2]!.clientKey, '第三行失败'],
[rows[0]!.clientKey, '第一行失败'],
]),
);
});
it('returns no rows or errors when every row succeeds', () => {
const result = applyBatchResult([createBatchRow()], {
successCount: 1,
failureCount: 0,
failures: [],
});
expect(result).toEqual({ rows: [], rowErrors: new Map() });
});
});

View File

@@ -0,0 +1,94 @@
import type { LmsSubPackageRelationApi } from '#/api/lms/subpackagerelation';
export interface BatchFormRow
extends LmsSubPackageRelationApi.BatchCreateItem {}
export type BatchFieldErrors = Partial<
Record<keyof BatchFormRow, string>
>;
let fallbackSequence = 0;
function createClientKey() {
const randomUUID = globalThis.crypto?.randomUUID;
if (randomUUID) {
return randomUUID.call(globalThis.crypto);
}
fallbackSequence += 1;
return `${Date.now()}-${fallbackSequence}`;
}
export function createBatchRow(): BatchFormRow {
return {
clientKey: createClientKey(),
packageBoxSn: '',
boxType: '',
boxLength: '',
boxWidth: '',
boxHigh: '',
qualityGuaranPeriod: '',
dateOfFgInbound: '',
containerName: '',
status: '0',
};
}
export function copyBatchRow(source: BatchFormRow): BatchFormRow {
return {
clientKey: createClientKey(),
packageBoxSn: '',
boxType: source.boxType,
boxLength: source.boxLength,
boxWidth: source.boxWidth,
boxHigh: source.boxHigh,
qualityGuaranPeriod: source.qualityGuaranPeriod,
dateOfFgInbound: source.dateOfFgInbound,
containerName: '',
status: source.status,
};
}
export function validateBatchRows(
rows: BatchFormRow[],
): Map<number, BatchFieldErrors> {
const result = new Map<number, BatchFieldErrors>();
const requiredFields: [keyof BatchFormRow, string][] = [
['packageBoxSn', '木箱唯一码不能为空'],
['boxType', '木箱类型不能为空'],
['qualityGuaranPeriod', '保质期不能为空'],
['dateOfFgInbound', '入库日期不能为空'],
['containerName', '子卷号不能为空'],
['status', '状态不能为空'],
];
rows.forEach((row, index) => {
const errors: BatchFieldErrors = {};
requiredFields.forEach(([field, message]) => {
if (row[field]?.trim().length === 0) {
errors[field] = message;
}
});
if (Object.keys(errors).length > 0) {
result.set(index, errors);
}
});
return result;
}
export function applyBatchResult(
rows: BatchFormRow[],
result: LmsSubPackageRelationApi.BatchCreateResult,
): { rows: BatchFormRow[]; rowErrors: Map<string, string> } {
const failedClientKeys = new Set(
result.failures.map((failure) => failure.clientKey),
);
const rowErrors = new Map(
result.failures.map((failure) => [failure.clientKey, failure.message]),
);
return {
rows: rows.filter((row) => failedClientKeys.has(row.clientKey)),
rowErrors,
};
}

View File

@@ -316,6 +316,7 @@ const LMS_DICT = {
BOX_POINT_TYPE: 'box_point_type', // 装箱点位类型
BOX_POINT_STATUS: 'box_point_status', // 装箱点位状态
BOX_POINT_DEPTH: 'box_point_depth', // 装箱点位深浅
SUB_PACKAGE_STATUS: 'sub_package_status', // 子卷包装关系状态
} as const;
/** 字典类型枚举 - 统一导出 */