ForgeFlow-ERP/frontend/scripts/test-stocktake-workflow.mjs
2026-06-12 16:00:56 +08:00

187 lines
8.9 KiB
JavaScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import {
canContinueStocktake,
diffTone,
filterStocktakesForWarehouse,
findActiveStocktakeForWarehouse,
findOnlyActiveStocktake,
hasBlockingDiffErrors,
isEmptyStocktakeRead,
nextStocktakePanel,
splitDiffLines,
stocktakeBelongsToWarehouse,
stocktakeSummaryLineCount,
warehouseStocktakeStatus
} from "../src/utils/stocktakeWorkflow.js";
const emptyExported = {
id: 10,
status: "EXPORTED",
summary: {
match_count: 0,
gain_count: 0,
loss_count: 0,
new_lot_count: 0,
missing_count: 0,
error_count: 0
},
warehouses: [{ warehouse_id: 4, warehouse_name: "辅料库" }]
};
const importedWithDiff = {
id: 11,
status: "IMPORTED",
summary: {
match_count: 2,
gain_count: 1,
loss_count: 1,
new_lot_count: 0,
missing_count: 0,
error_count: 0
},
warehouses: [{ warehouse_id: 1, warehouse_name: "原材料库" }]
};
const lockedRaw = {
id: 12,
status: "LOCKED",
stocktake_no: "PK-20260530-012",
warehouses: [{ warehouse_id: 1, warehouse_name: "原材料库" }]
};
assert.equal(stocktakeSummaryLineCount(emptyExported.summary), 0);
assert.equal(isEmptyStocktakeRead(emptyExported), true);
assert.equal(isEmptyStocktakeRead(importedWithDiff), false);
assert.equal(isEmptyStocktakeRead({ status: "EXPORTED" }), false);
assert.equal(nextStocktakePanel(null), "overview");
assert.equal(nextStocktakePanel({ status: "LOCKED" }), "export");
assert.equal(nextStocktakePanel({ status: "EXPORTED" }), "import");
assert.equal(nextStocktakePanel(emptyExported), "diff");
assert.equal(nextStocktakePanel({ ...emptyExported, summary: { match_count: 1 } }), "import");
assert.equal(nextStocktakePanel(importedWithDiff), "diff");
assert.equal(nextStocktakePanel({ status: "CONFIRMED" }), "history");
assert.equal(canContinueStocktake({ status: "LOCKED" }), true);
assert.equal(canContinueStocktake({ status: "EXPORTED" }), true);
assert.equal(canContinueStocktake({ status: "IMPORTED" }), true);
assert.equal(canContinueStocktake({ status: " imported " }), true);
assert.equal(canContinueStocktake({ status: "CONFIRMED" }), false);
assert.equal(findActiveStocktakeForWarehouse([importedWithDiff, lockedRaw], 1).id, 11);
assert.equal(findActiveStocktakeForWarehouse([emptyExported], 4).id, 10);
assert.equal(findActiveStocktakeForWarehouse([emptyExported], 999), null);
assert.equal(findActiveStocktakeForWarehouse([{ status: "LOCKED", warehouses: null }], 1), null);
assert.equal(stocktakeBelongsToWarehouse(lockedRaw, 1), true);
assert.equal(stocktakeBelongsToWarehouse(lockedRaw, 4), false);
assert.deepEqual(filterStocktakesForWarehouse([lockedRaw, emptyExported], 1).map((row) => row.id), [12]);
assert.equal(findOnlyActiveStocktake([lockedRaw]).id, 12);
assert.equal(findOnlyActiveStocktake([lockedRaw, importedWithDiff]), null);
assert.equal(findOnlyActiveStocktake([{ status: "CONFIRMED" }]), null);
const lines = [
{ id: 1, diff_type: "MATCH", row_status: "READY" },
{ id: 2, diff_type: "LOSS", row_status: "READY" },
{ id: 3, diff_type: "GAIN", row_status: "READY" },
{ id: 4, diff_type: "MATCH", row_status: "ERROR" }
];
assert.deepEqual(splitDiffLines(lines, "ALL").map((line) => line.id), [4, 2, 3, 1]);
assert.deepEqual(splitDiffLines(lines, "DIFF").map((line) => line.id), [4, 2, 3]);
assert.deepEqual(splitDiffLines(lines, "ERROR").map((line) => line.id), [4]);
assert.deepEqual(lines.map((line) => line.id), [1, 2, 3, 4]);
assert.equal(hasBlockingDiffErrors(lines), true);
assert.equal(diffTone({ diff_type: "GAIN", row_status: "READY" }), "gain");
assert.equal(diffTone({ diff_type: "LOSS", row_status: "READY" }), "loss");
assert.equal(diffTone({ diff_type: "NEW_LOT", row_status: "READY" }), "new");
assert.equal(diffTone({ diff_type: "MISSING", row_status: "READY" }), "missing");
assert.equal(diffTone({ diff_type: "MATCH", row_status: "ERROR" }), "error");
assert.equal(diffTone({ diff_type: "MATCH", row_status: "READY" }), "match");
const warehouseStatus = warehouseStocktakeStatus(
{ id: 1, warehouse_name: "原材料库", warehouse_type: "RAW" },
[lockedRaw]
);
assert.equal(warehouseStatus.locked, true);
assert.equal(warehouseStatus.activeStocktake.stocktake_no, "PK-20260530-012");
assert.equal(warehouseStatus.label, "盘库中");
const componentSource = readFileSync(
fileURLToPath(new URL("../src/components/StocktakeDialog.vue", import.meta.url)),
"utf8"
);
assert.match(componentSource, /async function applyLoadedStocktake\(stocktake, options = \{\}\)/);
assert.match(componentSource, /await applyLoadedStocktake\(stocktake, options\);/);
assert.match(componentSource, /await applyLoadedStocktake\(exportedStocktake, \{ silent: true \}\);/);
assert.doesNotMatch(componentSource, /findOnlyActiveStocktake\(historyRows\.value\)/);
assert.match(componentSource, /findActiveStocktakeForWarehouse\(historyRows\.value, currentWarehouseId\.value\)/);
assert.match(componentSource, /\/inventory\/stocktakes\?warehouse_id=\$\{currentWarehouseId\.value\}/);
assert.match(componentSource, /import PaginationBar from "\.\/PaginationBar\.vue";/);
assert.match(componentSource, /import \{ usePagination \} from "\.\.\/utils\/pagination";/);
assert.match(componentSource, /usePagination\(currentWarehouseHistoryRows, 6\)/);
assert.match(componentSource, /class="stocktake-main"/);
assert.match(componentSource, /class="stocktake-flow-column"/);
assert.match(componentSource, /:page-size-options="\[6\]"/);
assert.match(componentSource, /v-for="row in paginatedHistoryRows"/);
assert.doesNotMatch(componentSource, /v-for="row in currentWarehouseHistoryRows"/);
assert.doesNotMatch(componentSource, /<th>操作<\/th>/);
assert.doesNotMatch(componentSource, /继续处理\s*<\/button>/);
assert.doesNotMatch(componentSource, /v-if="canContinueStocktake\(row\)"/);
assert.match(componentSource, /hasBlockingDiffErrors\(previewLines\.value\)/);
assert.match(componentSource, /盘点清单已导入:发现/);
const styleSource = readFileSync(
fileURLToPath(new URL("../src/styles/main.css", import.meta.url)),
"utf8"
);
assert.match(styleSource, /\.stocktake-diff-modal \.stocktake-diff-table-v2[\s\S]*margin: 14px 18px 0;/);
assert.match(styleSource, /\.stocktake-diff-modal \.stocktake-footer[\s\S]*min-height: 72px;/);
assert.match(styleSource, /\.stocktake-diff-modal-mask[\s\S]*box-sizing: border-box;/);
assert.match(styleSource, /\.stocktake-diff-modal-mask[\s\S]*z-index: 3;/);
assert.match(styleSource, /\.stocktake-diff-modal[\s\S]*height: min\(820px, 100%\);/);
assert.match(styleSource, /\.stocktake-diff-modal[\s\S]*max-height: 100%;/);
assert.doesNotMatch(styleSource, /\.stocktake-diff-modal[\s\S]*calc\(100vh - 64px\)/);
assert.match(styleSource, /\.stocktake-diff-modal \.stocktake-footer[\s\S]*justify-content: flex-end;/);
assert.match(styleSource, /\.stocktake-confirm-mask[\s\S]*z-index: 4;/);
assert.match(styleSource, /\.stocktake-diff-cell,[\s\S]*overflow-wrap: anywhere;/);
assert.match(styleSource, /\.stocktake-diff-cell span,[\s\S]*line-height: 1\.55;/);
const applyLoadedStart = componentSource.indexOf("async function applyLoadedStocktake(stocktake, options = {})");
const activeStocktakeAssignment = componentSource.indexOf("activeStocktake.value = stocktake;", applyLoadedStart);
const clearConfirmDialogAssignment = componentSource.indexOf("confirmDialogOpen.value = false;", applyLoadedStart);
const clearPreviewLinesAssignment = componentSource.indexOf("previewLines.value = [];", applyLoadedStart);
const clearActiveStocktakeAssignment = componentSource.indexOf("activeStocktake.value = null;", applyLoadedStart);
const clearPreviewSummaryAssignment = componentSource.indexOf("previewSummary.value = {};", applyLoadedStart);
const lineFetchAssignment = componentSource.indexOf(
"fetchResource(`/inventory/stocktakes/${stocktake.id}/lines?warehouse_id=${currentWarehouseId.value}`, null)",
applyLoadedStart
);
const lineFetchGuard = componentSource.indexOf('throw new Error("读取盘库明细失败,请重新打开盘库单");', applyLoadedStart);
const activePanelAssignment = componentSource.indexOf(
'activePanel.value = nextPanel === "diff" || nextPanel === "history" ? "import" : nextPanel;',
applyLoadedStart
);
assert.notEqual(applyLoadedStart, -1);
assert.notEqual(activeStocktakeAssignment, -1);
assert.notEqual(clearConfirmDialogAssignment, -1);
assert.notEqual(clearPreviewLinesAssignment, -1);
assert.notEqual(clearActiveStocktakeAssignment, -1);
assert.notEqual(clearPreviewSummaryAssignment, -1);
assert.notEqual(lineFetchAssignment, -1);
assert.notEqual(lineFetchGuard, -1);
assert.notEqual(activePanelAssignment, -1);
assert.ok(clearConfirmDialogAssignment < lineFetchAssignment);
assert.ok(clearPreviewLinesAssignment < lineFetchAssignment);
assert.ok(clearActiveStocktakeAssignment < lineFetchAssignment);
assert.ok(clearPreviewSummaryAssignment < lineFetchAssignment);
assert.ok(lineFetchAssignment < activeStocktakeAssignment);
assert.ok(lineFetchAssignment < activePanelAssignment);
console.log("stocktake workflow tests passed");