fix: 防止批量结果映射丢失输入
This commit is contained in:
@@ -74,6 +74,18 @@ describe('batch-form-model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('reports a required field when its runtime value is missing', () => {
|
||||
const row = createBatchRow();
|
||||
const incompleteRow = {
|
||||
...row,
|
||||
containerName: undefined,
|
||||
} as unknown as typeof row;
|
||||
|
||||
expect(validateBatchRows([incompleteRow]).get(0)).toMatchObject({
|
||||
containerName: '子卷号不能为空',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not report an error for a complete row', () => {
|
||||
const row = Object.assign(createBatchRow(), {
|
||||
packageBoxSn: 'BOX-001',
|
||||
@@ -95,13 +107,13 @@ describe('batch-form-model', () => {
|
||||
failures: [
|
||||
{
|
||||
clientKey: rows[2]!.clientKey,
|
||||
rowIndex: 2,
|
||||
rowIndex: 3,
|
||||
errorCode: 'INVALID',
|
||||
message: '第三行失败',
|
||||
},
|
||||
{
|
||||
clientKey: rows[0]!.clientKey,
|
||||
rowIndex: 0,
|
||||
rowIndex: 1,
|
||||
errorCode: 'DUPLICATE',
|
||||
message: '第一行失败',
|
||||
},
|
||||
@@ -117,6 +129,94 @@ describe('batch-form-model', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to one-based row index when the failure client key is unknown', () => {
|
||||
const rows = [createBatchRow(), createBatchRow()];
|
||||
const result = applyBatchResult(rows, {
|
||||
successCount: 1,
|
||||
failureCount: 1,
|
||||
failures: [
|
||||
{
|
||||
clientKey: 'unknown-key',
|
||||
rowIndex: 2,
|
||||
errorCode: 'INVALID',
|
||||
message: '第二行失败',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.rows).toEqual([rows[1]]);
|
||||
expect(result.rowErrors).toEqual(
|
||||
new Map([[rows[1]!.clientKey, '第二行失败']]),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps every row when a known client key conflicts with row index', () => {
|
||||
const rows = [createBatchRow(), createBatchRow()];
|
||||
const result = applyBatchResult(rows, {
|
||||
successCount: 1,
|
||||
failureCount: 1,
|
||||
failures: [
|
||||
{
|
||||
clientKey: rows[0]!.clientKey,
|
||||
rowIndex: 2,
|
||||
errorCode: 'CONFLICT',
|
||||
message: '响应冲突',
|
||||
},
|
||||
],
|
||||
});
|
||||
const generalError = '批量保存结果无法匹配,请核对后重试';
|
||||
|
||||
expect(result.rows).toEqual(rows);
|
||||
expect(result.rowErrors).toEqual(
|
||||
new Map(rows.map((row) => [row.clientKey, generalError])),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps every row when an unknown client key has an invalid row index', () => {
|
||||
const rows = [createBatchRow(), createBatchRow()];
|
||||
const result = applyBatchResult(rows, {
|
||||
successCount: 1,
|
||||
failureCount: 1,
|
||||
failures: [
|
||||
{
|
||||
clientKey: 'unknown-key',
|
||||
rowIndex: 3,
|
||||
errorCode: 'INVALID_INDEX',
|
||||
message: '无法定位',
|
||||
},
|
||||
],
|
||||
});
|
||||
const generalError = '批量保存结果无法匹配,请核对后重试';
|
||||
|
||||
expect(result.rows).toEqual(rows);
|
||||
expect(result.rowErrors).toEqual(
|
||||
new Map(rows.map((row) => [row.clientKey, generalError])),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps every row when input client keys are duplicated', () => {
|
||||
const rows = [createBatchRow(), createBatchRow()];
|
||||
rows[1]!.clientKey = rows[0]!.clientKey;
|
||||
const result = applyBatchResult(rows, {
|
||||
successCount: 1,
|
||||
failureCount: 1,
|
||||
failures: [
|
||||
{
|
||||
clientKey: rows[0]!.clientKey,
|
||||
rowIndex: 1,
|
||||
errorCode: 'INVALID',
|
||||
message: '第一行失败',
|
||||
},
|
||||
],
|
||||
});
|
||||
const generalError = '批量保存结果无法匹配,请核对后重试';
|
||||
|
||||
expect(result.rows).toEqual(rows);
|
||||
expect(result.rowErrors).toEqual(
|
||||
new Map(rows.map((row) => [row.clientKey, generalError])),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns no rows or errors when every row succeeds', () => {
|
||||
const result = applyBatchResult([createBatchRow()], {
|
||||
successCount: 1,
|
||||
|
||||
@@ -64,7 +64,7 @@ export function validateBatchRows(
|
||||
rows.forEach((row, index) => {
|
||||
const errors: BatchFieldErrors = {};
|
||||
requiredFields.forEach(([field, message]) => {
|
||||
if (row[field]?.trim().length === 0) {
|
||||
if (!row[field]?.trim()) {
|
||||
errors[field] = message;
|
||||
}
|
||||
});
|
||||
@@ -80,12 +80,41 @@ 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]),
|
||||
);
|
||||
if (result.failures.length === 0) {
|
||||
return { rows: [], rowErrors: new Map() };
|
||||
}
|
||||
|
||||
const generalError = '批量保存结果无法匹配,请核对后重试';
|
||||
const rowByClientKey = new Map<string, BatchFormRow>();
|
||||
for (const row of rows) {
|
||||
if (rowByClientKey.has(row.clientKey)) {
|
||||
return {
|
||||
rows,
|
||||
rowErrors: new Map(rows.map((item) => [item.clientKey, generalError])),
|
||||
};
|
||||
}
|
||||
rowByClientKey.set(row.clientKey, row);
|
||||
}
|
||||
|
||||
const failedClientKeys = new Set<string>();
|
||||
const rowErrors = new Map<string, string>();
|
||||
for (const failure of result.failures) {
|
||||
const rowByKey = rowByClientKey.get(failure.clientKey);
|
||||
const rowByIndex = rows[failure.rowIndex - 1];
|
||||
if (
|
||||
(rowByKey && rowByIndex?.clientKey !== rowByKey.clientKey) ||
|
||||
(!rowByKey && !rowByIndex)
|
||||
) {
|
||||
return {
|
||||
rows,
|
||||
rowErrors: new Map(rows.map((item) => [item.clientKey, generalError])),
|
||||
};
|
||||
}
|
||||
|
||||
const matchedRow = rowByKey ?? rowByIndex!;
|
||||
failedClientKeys.add(matchedRow.clientKey);
|
||||
rowErrors.set(matchedRow.clientKey, failure.message);
|
||||
}
|
||||
|
||||
return {
|
||||
rows: rows.filter((row) => failedClientKeys.has(row.clientKey)),
|
||||
|
||||
Reference in New Issue
Block a user