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', () => {
|
it('does not report an error for a complete row', () => {
|
||||||
const row = Object.assign(createBatchRow(), {
|
const row = Object.assign(createBatchRow(), {
|
||||||
packageBoxSn: 'BOX-001',
|
packageBoxSn: 'BOX-001',
|
||||||
@@ -95,13 +107,13 @@ describe('batch-form-model', () => {
|
|||||||
failures: [
|
failures: [
|
||||||
{
|
{
|
||||||
clientKey: rows[2]!.clientKey,
|
clientKey: rows[2]!.clientKey,
|
||||||
rowIndex: 2,
|
rowIndex: 3,
|
||||||
errorCode: 'INVALID',
|
errorCode: 'INVALID',
|
||||||
message: '第三行失败',
|
message: '第三行失败',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
clientKey: rows[0]!.clientKey,
|
clientKey: rows[0]!.clientKey,
|
||||||
rowIndex: 0,
|
rowIndex: 1,
|
||||||
errorCode: 'DUPLICATE',
|
errorCode: 'DUPLICATE',
|
||||||
message: '第一行失败',
|
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', () => {
|
it('returns no rows or errors when every row succeeds', () => {
|
||||||
const result = applyBatchResult([createBatchRow()], {
|
const result = applyBatchResult([createBatchRow()], {
|
||||||
successCount: 1,
|
successCount: 1,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export function validateBatchRows(
|
|||||||
rows.forEach((row, index) => {
|
rows.forEach((row, index) => {
|
||||||
const errors: BatchFieldErrors = {};
|
const errors: BatchFieldErrors = {};
|
||||||
requiredFields.forEach(([field, message]) => {
|
requiredFields.forEach(([field, message]) => {
|
||||||
if (row[field]?.trim().length === 0) {
|
if (!row[field]?.trim()) {
|
||||||
errors[field] = message;
|
errors[field] = message;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -80,12 +80,41 @@ export function applyBatchResult(
|
|||||||
rows: BatchFormRow[],
|
rows: BatchFormRow[],
|
||||||
result: LmsSubPackageRelationApi.BatchCreateResult,
|
result: LmsSubPackageRelationApi.BatchCreateResult,
|
||||||
): { rows: BatchFormRow[]; rowErrors: Map<string, string> } {
|
): { rows: BatchFormRow[]; rowErrors: Map<string, string> } {
|
||||||
const failedClientKeys = new Set(
|
if (result.failures.length === 0) {
|
||||||
result.failures.map((failure) => failure.clientKey),
|
return { rows: [], rowErrors: new Map() };
|
||||||
);
|
}
|
||||||
const rowErrors = new Map(
|
|
||||||
result.failures.map((failure) => [failure.clientKey, failure.message]),
|
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 {
|
return {
|
||||||
rows: rows.filter((row) => failedClientKeys.has(row.clientKey)),
|
rows: rows.filter((row) => failedClientKeys.has(row.clientKey)),
|
||||||
|
|||||||
Reference in New Issue
Block a user