feat: 增加包装关系批量录入模型
This commit is contained in:
@@ -3,6 +3,35 @@ import type { PageParam, PageResult } from '@vben/request';
|
|||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace LmsSubPackageRelationApi {
|
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 {
|
export interface SubPackageRelation {
|
||||||
id: number; // 子卷包装标识
|
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) {
|
export function getSubPackageRelationPage(params: PageParam) {
|
||||||
return requestClient.get<PageResult<LmsSubPackageRelationApi.SubPackageRelation>>(
|
return requestClient.get<PageResult<LmsSubPackageRelationApi.SubPackageRelation>>(
|
||||||
|
|||||||
@@ -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() });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -316,6 +316,7 @@ const LMS_DICT = {
|
|||||||
BOX_POINT_TYPE: 'box_point_type', // 装箱点位类型
|
BOX_POINT_TYPE: 'box_point_type', // 装箱点位类型
|
||||||
BOX_POINT_STATUS: 'box_point_status', // 装箱点位状态
|
BOX_POINT_STATUS: 'box_point_status', // 装箱点位状态
|
||||||
BOX_POINT_DEPTH: 'box_point_depth', // 装箱点位深浅
|
BOX_POINT_DEPTH: 'box_point_depth', // 装箱点位深浅
|
||||||
|
SUB_PACKAGE_STATUS: 'sub_package_status', // 子卷包装关系状态
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/** 字典类型枚举 - 统一导出 */
|
/** 字典类型枚举 - 统一导出 */
|
||||||
|
|||||||
Reference in New Issue
Block a user