Files
FTC25009/scripts/verify-outbound-stock-fixes.js

209 lines
7.7 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const root = path.resolve(__dirname, '..');
function read(relativePath) {
return fs.readFileSync(path.join(root, relativePath), 'utf8');
}
function assertContains(file, needle, message) {
const content = read(file);
if (!content.includes(needle)) {
throw new Error(`${message}\nMissing in ${file}: ${needle}`);
}
}
function assertMatches(file, pattern, message) {
const content = read(file);
if (!pattern.test(content)) {
throw new Error(`${message}\nPattern not found in ${file}: ${pattern}`);
}
}
assertMatches(
'ruoyi-admin/src/main/java/com/ruoyi/bas/stock/service/impl/BasStockServiceImpl.java',
/"KC"\s*\+\s*STOCK_ID_FORMATTER\.format/,
'Manual stock creation must generate KC + millisecond timestamp IDs.'
);
assertContains(
'sql/bas_stock_master_detail.sql',
"in_type",
'Inbound completion stock generation must persist the real inbound type.'
);
assertMatches(
'sql/bas_stock_master_detail.sql',
/'KC'\s*\+\s*FORMAT\(@now,\s*'yyyyMMddHHmmssfff'\)/,
'Inbound completion procedure must generate KC + millisecond timestamp stock IDs.'
);
assertContains(
'sql/bas_stock_master_detail.sql',
"@stock_id + N'-' + CONVERT",
'Inbound completion stock detail IDs must use stock ID plus detail sequence.'
);
assertContains(
'ruoyi-admin/src/main/resources/mapper/bas/BasStockDetailMapper.xml',
'bsd.product_id, bsd.product_name',
'Stock detail queries must keep material code and material name in separate columns.'
);
assertContains(
'ruoyi-ui/src/views/bas/stock/index.vue',
'label="物料编码" align="center" prop="productId"',
'Stock detail UI must render material code from productId.'
);
assertContains(
'ruoyi-ui/src/views/bas/stock/index.vue',
'label="物料名称" align="center" prop="productName"',
'Stock detail UI must render material name from productName.'
);
const outJob = read('ruoyi-ui/src/views/out/job/index.vue');
if (!outJob.includes('prop="status"') || !outJob.includes('dict.type.mydo_job_status')) {
throw new Error('Outbound job page must filter/render task status with mydo_job_status.');
}
if (!outJob.includes('dict.type.out_job_type') || !outJob.includes("dicts: ['mydo_job_execution_phase', 'mydo_job_status', 'out_job_type']")) {
throw new Error('Outbound job page must render task type with out_job_type.');
}
if (!outJob.includes("parseTime(scope.row.createTime")) {
throw new Error('Outbound job page must render createTime from createTime, not createDate.');
}
if (!outJob.includes('this.detailQueryParams.status = row.status')) {
throw new Error('Finished outbound jobs must pass parent status when loading child job details.');
}
assertContains(
'ruoyi-ui/src/views/out/stockout/index.vue',
'selectedExitId',
'Outbound allocation UI must require an exit selection.'
);
assertContains(
'ruoyi-ui/src/views/out/stockout/index.vue',
'listStationPoint',
'Outbound allocation UI must load warehouse exit/entry-exit station points.'
);
assertContains(
'ruoyi-ui/src/views/out/stockout/index.vue',
'this.detailQueryParams.status = row.status',
'Finished outbound bills must pass the parent status when loading child details.'
);
assertContains(
'ruoyi-admin/src/main/java/com/ruoyi/out/stockout/domain/OutStockoutManualAssignDTO.java',
'private String exitId;',
'Manual outbound allocation DTO must carry the selected exit.'
);
assertContains(
'ruoyi-admin/src/main/resources/mapper/out/OutStockoutMapper.xml',
'#{v_exit_id,mode=IN,jdbcType=VARCHAR}',
'Outbound allocation mapper must pass the selected exit to procedures.'
);
assertContains(
'sql/out_stockout_flow.sql',
'@v_exit_id varchar(80)',
'Outbound allocation procedures must accept a selected exit.'
);
assertContains(
'sql/out_stockout_flow.sql',
'@exit_id = @v_exit_id',
'Outbound allocation procedure must store the selected exit on generated jobs.'
);
assertContains(
'ruoyi-admin/src/main/java/com/ruoyi/in/job/service/impl/MydoJobServiceImpl.java',
'requestWcsCancelBeforeManualCancel',
'Manual job cancellation must reserve a WCS cancellation check.'
);
assertContains(
'pda/pages/warehouse/outbound/confirm.vue',
'扫描托盘条码',
'PDA outbound confirmation must scan tray barcodes.'
);
assertContains(
'pda/pages/warehouse/outbound/confirm.vue',
'materials',
'PDA outbound confirmation must render tray material details.'
);
assertContains(
'pda/pages/warehouse/outbound/confirm.vue',
'v-model="item.actualQuantity"',
'PDA outbound confirmation must capture actual outbound quantity per material.'
);
assertContains(
'pda/pages/warehouse/outbound/confirm.vue',
'materials: this.outboundInfo.materials.map',
'PDA outbound confirmation must submit actual quantities per material.'
);
assertContains(
'ruoyi-admin/src/main/java/com/ruoyi/pda/service/impl/PdaServiceImpl.java',
'createReturnInboundJobIfNeeded',
'PDA outbound confirmation must create return inbound jobs for residual tray materials.'
);
assertContains(
'ruoyi-admin/src/main/java/com/ruoyi/pda/service/impl/PdaServiceImpl.java',
'jobIdService.getId("HL")',
'Residual tray return jobs must use the 回流作业 job-id prefix.'
);
assertContains(
'ruoyi-admin/src/main/java/com/ruoyi/pda/service/impl/PdaServiceImpl.java',
'taskNoService.getTaskNo("5")',
'Residual tray return jobs must use the 回流入库 task-number type.'
);
assertContains(
'sql/20260624_update_job_dicts.sql',
"N'回流入库'",
'Inbound job dictionaries must render 回流入库 return jobs.'
);
assertContains(
'ruoyi-admin/src/main/resources/mapper/pda/PdaMapper.xml',
'selectResidualTrayMaterials',
'PDA mapper must query residual tray materials after outbound confirmation.'
);
assertContains(
'ruoyi-ui/src/views/out/locationOutbound/index.vue',
'active-text="所有货位"',
'Specified-location outbound filter switch must be labelled 所有货位.'
);
assertContains(
'ruoyi-ui/src/views/out/locationOutbound/index.vue',
'listWarehouse',
'Specified-location outbound page must load warehouse dropdown options.'
);
assertContains(
'ruoyi-ui/src/views/out/locationOutbound/index.vue',
'v-model="queryParams.warehouseId"',
'Specified-location outbound page must filter by warehouse id dropdown.'
);
assertContains(
'ruoyi-ui/src/views/out/locationOutbound/index.vue',
'@selection-change="handleSelectionChange"',
'Specified-location outbound table must support selecting locations.'
);
assertContains(
'ruoyi-ui/src/views/out/locationOutbound/index.vue',
'批量生成作业',
'Specified-location outbound page must provide a batch generation action.'
);
assertContains(
'ruoyi-ui/src/views/out/locationOutbound/index.vue',
'Promise.all(rows.map',
'Specified-location outbound page must generate outbound jobs for selected locations.'
);
const locationOutbound = read('ruoyi-ui/src/views/out/locationOutbound/index.vue');
if (locationOutbound.includes('type="expand"')) {
throw new Error('Specified-location outbound page must use a lower child-detail table instead of expand rows.');
}
if (!locationOutbound.includes('@row-click="handleRowClick"') ||
!locationOutbound.includes('currentLocation') ||
!locationOutbound.includes('locationDetailList')) {
throw new Error('Specified-location outbound page must click a parent location row and show child details below.');
}
if (!locationOutbound.includes('cargoSpaceId: row.cargoSpaceId')) {
throw new Error('Specified-location outbound generation must send cargoSpaceId so empty locations can generate jobs.');
}
if (!locationOutbound.includes("['1', '6'].includes(String(row.cargoSpaceStatus))")) {
throw new Error('Specified-location outbound must allow empty and no-stock abnormal locations to generate jobs.');
}
console.log('Outbound and stock regression checks passed.');