15ddac178c
- Updated production.spec.ts, stock-adjustments.spec.ts, and stock-transfers.spec.ts to eliminate UOM references in API seeder and test cases. - Adjusted ApiSeeder methods to remove UOM parameters from stock receiving and production template creation. - Revised documentation to reflect changes in UOM handling, emphasizing that stock is counted in base UOM only. - Introduced new enums for MeasureUnit and StageQtyUnit to clarify content size and stage input quantities. - Implemented ItemContent service to validate and normalize content sizes. - Updated smoke tests to validate production stage inputs expressed in content units, ensuring correct consumption calculations. - Modified frontend UOM label handling to reflect the removal of per-line UOMs in document lines.
89 lines
3.2 KiB
TypeScript
89 lines
3.2 KiB
TypeScript
import { test, expect, APIRequestContext } from "@playwright/test"
|
|
import { ApiSeeder, newApiContext, Warehouse, Item, Vendor } from "../support/api"
|
|
import { StockTransferNewPage, StockTransferDetailPage } from "../pages/StockPages"
|
|
|
|
// Stock transfer flow (Backend/ERPCore/Controllers/StockTransfersController.cs, Frontend
|
|
// app/dashboard/stock/transfers/*): Draft -> Dispatch -> Receive, moving FIFO layers
|
|
// between warehouses.
|
|
test.describe("Stock transfers", () => {
|
|
let api: APIRequestContext
|
|
let seeder: ApiSeeder
|
|
let srcWarehouse: Warehouse
|
|
let destWarehouse: Warehouse
|
|
let vendor: Vendor
|
|
let item: Item
|
|
|
|
test.beforeAll(async () => {
|
|
api = await newApiContext()
|
|
seeder = new ApiSeeder(api)
|
|
srcWarehouse = await seeder.firstWarehouse()
|
|
destWarehouse = await seeder.secondWarehouse()
|
|
vendor = await seeder.createVendor()
|
|
item = await seeder.createItem({ namePrefix: "Transfer Test Item" })
|
|
|
|
await seeder.receiveStock({
|
|
warehouseId: srcWarehouse.warehouseId,
|
|
vendorId: vendor.vendorId,
|
|
itemId: item.itemId,
|
|
|
|
qty: 50,
|
|
unitCost: 15,
|
|
})
|
|
})
|
|
|
|
test.afterAll(async () => {
|
|
await api.dispose()
|
|
})
|
|
|
|
test("create -> dispatch -> receive moves stock between warehouses", async ({ page }) => {
|
|
const before = await seeder.stockOnHand(item.itemId, srcWarehouse.warehouseId)
|
|
|
|
const newPage = new StockTransferNewPage(page)
|
|
await newPage.goto()
|
|
await newPage.fill({ fromWarehouse: srcWarehouse.name, toWarehouse: destWarehouse.name, item: item.name, qty: 10 })
|
|
await newPage.submit()
|
|
|
|
await expect(page).toHaveURL(/\/dashboard\/stock\/transfers\/\d+/)
|
|
const detail = new StockTransferDetailPage(page)
|
|
await detail.expectStatus("Draft")
|
|
|
|
await detail.dispatch()
|
|
await detail.expectStatus("InTransit")
|
|
|
|
const afterDispatch = await seeder.stockOnHand(item.itemId, srcWarehouse.warehouseId)
|
|
// Dispatch consumes the source FIFO layers immediately (Backend/ERPCore/Services/Stock/
|
|
// StockService.cs: "Dispatch already consumed the source layers, so this stock has left
|
|
// onHand") - inTransit is reported for visibility only, not held back from onHand.
|
|
expect(afterDispatch.onHand).toBeCloseTo(before.onHand - 10, 4)
|
|
|
|
await detail.receive()
|
|
await detail.expectStatus("Received")
|
|
|
|
const destAfter = await seeder.stockOnHand(item.itemId, destWarehouse.warehouseId)
|
|
expect(destAfter.onHand).toBeGreaterThanOrEqual(10)
|
|
})
|
|
|
|
test("dispatch fails with insufficient stock and the transfer stays Draft", async ({ page }) => {
|
|
const newPage = new StockTransferNewPage(page)
|
|
await newPage.goto()
|
|
await newPage.fill({
|
|
fromWarehouse: srcWarehouse.name,
|
|
toWarehouse: destWarehouse.name,
|
|
item: item.name,
|
|
qty: 999_999,
|
|
})
|
|
await newPage.submit()
|
|
|
|
await expect(page).toHaveURL(/\/dashboard\/stock\/transfers\/\d+/)
|
|
const detail = new StockTransferDetailPage(page)
|
|
await detail.expectStatus("Draft")
|
|
|
|
await detail.dispatch()
|
|
|
|
// FifoCostingService rejects with 409 STOCK_NEGATIVE_BLOCKED - the frontend surfaces the
|
|
// error and leaves the transfer in Draft rather than advancing it.
|
|
await detail.expectStatus("Draft")
|
|
await expect(page.getByRole("button", { name: /^dispatch$/i })).toBeVisible()
|
|
})
|
|
})
|