import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { buildGenericItemOptions, buildProductOperationOptions } from "../src/utils/inventoryOperationOptions.js"; const stockedBalances = [ { item_id: 101, item_code: "原材料00001", item_name: "304不锈钢", warehouse_id: 1, warehouse_type: "RAW", weight_available_kg: 100, qty_available: 0, avg_unit_cost: 4 }, { item_id: 101, item_code: "原材料00001", item_name: "304不锈钢", warehouse_id: 1, warehouse_type: "RAW", weight_available_kg: 40, qty_available: 0, avg_unit_cost: 5 }, { item_id: 102, item_code: "原材料00002", item_name: "冷轧板", warehouse_id: 1, warehouse_type: "RAW", weight_available_kg: 0, qty_available: 0 }, { item_id: 103, item_code: "原材料00003", item_name: "铝合金", warehouse_id: 2, warehouse_type: "RAW", weight_available_kg: 80, qty_available: 0 } ]; const rawOutboundOptions = buildGenericItemOptions({ direction: "out", activeInventoryTab: "raw", activeWarehouseType: "RAW", warehouseId: 1, balances: stockedBalances, materials: [], products: [] }); assert.deepEqual( rawOutboundOptions.map((item) => ({ item_id: item.item_id, item_code: item.item_code, item_name: item.item_name, weight_available_kg: item.weight_available_kg })), [ { item_id: 101, item_code: "原材料00001", item_name: "304不锈钢", weight_available_kg: 140 } ] ); const rawInboundOptions = buildGenericItemOptions({ direction: "in", activeInventoryTab: "raw", activeWarehouseType: "RAW", warehouseId: 1, balances: stockedBalances, materials: [{ item_id: 201, item_code: "原材料00004", item_name: "铜板" }], products: [{ item_id: 301, item_code: "产品00001", item_name: "支架" }] }); assert.deepEqual(rawInboundOptions.map((item) => item.item_id), [201]); const finishedOutboundOptions = buildGenericItemOptions({ direction: "out", activeInventoryTab: "finished", activeWarehouseType: "FINISHED", warehouseId: 8, balances: [ { item_id: 401, item_code: "产品需规00001", item_name: "电脑支架", warehouse_id: 8, warehouse_type: "FINISHED", qty_available: 12, weight_available_kg: 30 } ], materials: [], products: [] }); assert.equal(finishedOutboundOptions.length, 1); assert.equal(finishedOutboundOptions[0].qty_available, 12); const semiOutboundOptions = buildGenericItemOptions({ direction: "out", activeInventoryTab: "semi", activeWarehouseType: "SEMI", warehouseId: 9, balances: [ { item_id: 501, item_code: "产品需规00002", item_name: "半成品支架", source_line_id: 9101, option_key: "501::9101", operation_label: "产品:半成品支架;已完成工序:第1道 冲压", warehouse_id: 9, warehouse_type: "SEMI", qty_available: 6, weight_available_kg: 12 }, { item_id: 501, item_code: "产品需规00002", item_name: "半成品支架", source_line_id: 9102, option_key: "501::9102", operation_label: "产品:半成品支架;已完成工序:第2道 折弯", warehouse_id: 9, warehouse_type: "SEMI", qty_available: 8, weight_available_kg: 20 } ], materials: [], products: [] }); assert.equal(semiOutboundOptions.length, 2); assert.deepEqual(semiOutboundOptions.map((item) => item.option_key), ["501::9101", "501::9102"]); const productMasterRows = [{ item_id: 601, item_code: "产品需规00003", item_name: "委外成品" }]; const productOperationRows = [ { option_key: "601::7001", item_id: 601, item_code: "产品需规00003", item_name: "委外成品", route_id: 8001, route_operation_id: 7001, seq_no: 1, operation_name: "冲压" }, { option_key: "601::7002", item_id: 601, item_code: "产品需规00003", item_name: "委外成品", route_id: 8001, route_operation_id: 7002, seq_no: 2, operation_name: "折弯" } ]; const routeOperationOptions = buildProductOperationOptions([ { route_id: 8001, product_item_id: 601, product_code: "产品需规00003", product_name: "委外成品", version_no: "V1.0", status: "ACTIVE", operations: [ { route_operation_id: 7001, seq_no: 1, operation_name: "1", process_name: "1", stamping_method: "切边冲孔", status: "ACTIVE" } ] } ]); assert.equal(routeOperationOptions[0].operation_label, "第1道 切边冲孔"); assert.equal(routeOperationOptions[0].source_material_summary, "产品:委外成品;已完成工序:第1道 切边冲孔"); const operationWithoutStampingMethod = buildProductOperationOptions([ { route_id: 8002, product_item_id: 602, product_code: "产品需规00004", product_name: "无冲压方式产品", version_no: "V1.0", status: "ACTIVE", operations: [ { route_operation_id: 7003, seq_no: 1, operation_name: "1", process_name: "开平", stamping_method: "", status: "ACTIVE" } ] } ]); assert.equal(operationWithoutStampingMethod[0].operation_label, "第1道"); const semiInboundOptions = buildGenericItemOptions({ direction: "in", activeInventoryTab: "semi", activeWarehouseType: "SEMI", warehouseId: 9, balances: [], materials: [], products: productMasterRows, productOperations: productOperationRows }); const finishedInboundOptions = buildGenericItemOptions({ direction: "in", activeInventoryTab: "finished", activeWarehouseType: "FINISHED", warehouseId: 8, balances: [], materials: [], products: productMasterRows }); assert.deepEqual(semiInboundOptions.map((item) => item.option_key), ["601::7001", "601::7002"]); assert.deepEqual(semiInboundOptions.map((item) => item.route_operation_id), [7001, 7002]); assert.deepEqual(finishedInboundOptions.map((item) => item.item_id), [601]); const auxiliaryOutboundOptions = buildGenericItemOptions({ direction: "out", activeInventoryTab: "auxiliary", activeWarehouseType: "AUX", warehouseId: 10, balances: [ { item_id: 701, item_code: "辅料00001", item_name: "包装膜", warehouse_id: 10, warehouse_type: "AUX", qty_available: 0, weight_available_kg: 25 } ], materials: [], products: [] }); const auxiliaryInboundOptions = buildGenericItemOptions({ direction: "in", activeInventoryTab: "auxiliary", activeWarehouseType: "AUX", warehouseId: 10, balances: [], materials: [{ item_id: 702, item_code: "辅料00002", item_name: "手套" }], products: [] }); assert.equal(auxiliaryOutboundOptions.length, 1); assert.equal(auxiliaryOutboundOptions[0].item_id, 701); assert.deepEqual(auxiliaryInboundOptions.map((item) => item.item_id), [702]); const scrapInboundOptions = buildGenericItemOptions({ direction: "in", activeInventoryTab: "scrap", activeWarehouseType: "SCRAP", warehouseId: 11, balances: [], materials: [{ item_id: 801, item_code: "原材料00005", item_name: "冲压边角料" }], products: [{ item_id: 901, item_code: "产品需规00004", item_name: "报废支架" }] }); const scrapOutboundOptions = buildGenericItemOptions({ direction: "out", activeInventoryTab: "scrap", activeWarehouseType: "SCRAP", warehouseId: 11, balances: [ { item_id: 801, item_code: "原材料00005", item_name: "冲压边角料", warehouse_id: 11, warehouse_type: "SCRAP", qty_available: 10, weight_available_kg: 0 }, { item_id: 901, item_code: "产品需规00004", item_name: "报废支架", warehouse_id: 11, warehouse_type: "SCRAP", qty_available: 0, weight_available_kg: 42 }, { item_id: 902, item_code: "产品需规00005", item_name: "异仓废料", warehouse_id: 12, warehouse_type: "SCRAP", qty_available: 0, weight_available_kg: 18 } ], materials: [], products: [] }); const scrapWarehouseTypeInboundOptions = buildGenericItemOptions({ direction: "in", activeInventoryTab: "raw", activeWarehouseType: "SCRAP", warehouseId: 11, balances: [], materials: [{ item_id: 803, item_code: "原材料00006", item_name: "废铜" }], products: [{ item_id: 903, item_code: "产品需规00006", item_name: "报废夹具" }] }); assert.deepEqual(scrapInboundOptions.map((item) => item.item_id), [801, 901]); assert.deepEqual(scrapWarehouseTypeInboundOptions.map((item) => item.item_id), [803, 903]); assert.deepEqual( scrapOutboundOptions.map((item) => ({ item_id: item.item_id, qty_available: item.qty_available, weight_available_kg: item.weight_available_kg })), [ { item_id: 901, qty_available: 0, weight_available_kg: 42 } ] ); const inventoryLedgerSource = readFileSync( fileURLToPath(new URL("../src/views/InventoryLedgerView.vue", import.meta.url)), "utf8" ); const deliveryManagementSource = readFileSync( fileURLToPath(new URL("../src/views/DeliveryManagementView.vue", import.meta.url)), "utf8" ); const tableControlsSource = readFileSync( fileURLToPath(new URL("../src/components/TableControls.vue", import.meta.url)), "utf8" ); const stockLotTagSelectSource = readFileSync( fileURLToPath(new URL("../src/components/StockLotTagSelect.vue", import.meta.url)), "utf8" ); assert.match(inventoryLedgerSource, /key: "scrap"/); assert.match(inventoryLedgerSource, /label: "废料库"/); assert.match(inventoryLedgerSource, /PRODUCTION_SCRAP_IN/); assert.match( inventoryLedgerSource, /const genericUnitCostLabel = computed\(\(\) =>[\s\S]*isProductionScrapInbound\.value[\s\S]*原材料成本单价\(元\/kg\)/, "production scrap inbound should label unit cost as raw material cost unit price" ); assert.match( inventoryLedgerSource, /const productionScrapToleranceRemainingWeight = computed\(\(\) => \{[\s\S]*productionScrapExpectedWeight\.value[\s\S]*1\.15[\s\S]*productionScrapInboundBeforeWeight/, "production scrap inbound should calculate the non-settle upper limit from cumulative scrap inbound weight" ); assert.match( inventoryLedgerSource, /genericForm\.settle_work_order && productionScrapSettleNeedsRemark\.value/, "production scrap inbound should require a settle explanation when final scrap weight is outside tolerance" ); assert.match( inventoryLedgerSource, /const scrapToleranceMax = Number\(productionScrapToleranceRemainingWeight\.value \|\| 0\);[\s\S]*inboundWeight > scrapToleranceMax/, "production scrap inbound validation should compare inbound weight against cumulative upper-limit remaining amount" ); assert.match( inventoryLedgerSource, /activeOperation\?\.direction !== 'out' && !isCustomerSuppliedInbound && !isSourceLotReturnInbound && !isSemiSourceLotTraceInbound && !isFinishedOutsourcingInbound && !isProductionScrapInbound && !isReworkScrapInbound && !isOutsourcingScrapInbound/, "production scrap inbound should hide the manual batch number field" ); assert.match( inventoryLedgerSource, /lot_no: isCustomerSuppliedInbound\.value \? null : isProductionScrapInbound\.value \? null[\s\S]*isOutsourcingScrapInbound\.value \? null/, "production scrap inbound should never submit a manually-entered batch number" ); assert.match(inventoryLedgerSource, /OUTSOURCING_SCRAP_IN/); assert.match(inventoryLedgerSource, /SCRAP_SALE_OUT/); assert.match(inventoryLedgerSource, /LOGISTICS_REQUIRED_BIZ_TYPES/); assert.match(inventoryLedgerSource, /"CUSTOMER_SUPPLIED"/); assert.match(inventoryLedgerSource, /const isCustomerSuppliedInbound = computed/); assert.match(inventoryLedgerSource, /customerSuppliedLotNoPreview/); assert.match(inventoryLedgerSource, /提交后自动生成/); assert.match(inventoryLedgerSource, /activeOperation\?\.direction !== 'out' && !isCustomerSuppliedInbound/); assert.match(inventoryLedgerSource, /lot_no: isCustomerSuppliedInbound\.value \? null/); assert.match(inventoryLedgerSource, /source_material_sub_batch_no: isCustomerSuppliedInbound\.value\s+\?\s+null/); assert.match(inventoryLedgerSource, /const workOrderLedgerRows = ref\(\[\]\);/); assert.match(inventoryLedgerSource, /fetchResource\("\/production\/work-order-ledger\?limit=500", \[\]\)/); assert.match(inventoryLedgerSource, /workOrderLedgerRows\.value = workOrderLedgerRowData;/); assert.match( stockLotTagSelectSource, /issued_weight_kg:\s*normalizeSelectedIssuedWeight\(lot\)/, "StockLotTagSelect should preserve an option's issued_weight_kg instead of overwriting it with remaining weight" ); assert.match( stockLotTagSelectSource, /function displayWeight\(lot\)/, "StockLotTagSelect should support scenario-specific selected-tag weight display" ); assert.match( inventoryLedgerSource, /:display-weight-field="isProductionSurplusInbound \? 'issued_weight_kg' : 'remaining_weight_kg'"/, "production surplus inbound should display selected source lots by work-order issued weight" ); assert.doesNotMatch( inventoryLedgerSource, /