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>/); 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");