import { test, expect, APIRequestContext } from "@playwright/test" import { ApiSeeder, newApiContext, Vendor, Warehouse, Item } from "../support/api" import { GrnNewPage, GrnDetailPage } from "../pages/GrnPages" // GRN receiving flow (Backend/ERPCore/Controllers/GrnsController.cs, Frontend // app/dashboard/receiving/grn/*). Covers direct + against-PO receipts, confirm posting to // the stock ledger, and per-line hold-status actions. test.describe("GRN", () => { let api: APIRequestContext let seeder: ApiSeeder let warehouse: Warehouse let vendor: Vendor let item: Item test.beforeAll(async () => { api = await newApiContext() seeder = new ApiSeeder(api) warehouse = await seeder.firstWarehouse() vendor = await seeder.createVendor() item = await seeder.createItem({ namePrefix: "GRN Test Item" }) }) test.afterAll(async () => { await api.dispose() }) test("direct receipt creates a Draft GRN, confirm posts stock", async ({ page }) => { const grnNew = new GrnNewPage(page) await grnNew.goto() await grnNew.useDirectReceipt() await grnNew.selectVendor(vendor.name) await grnNew.selectWarehouse(warehouse.name) await grnNew.fillFirstLine({ item: item.name, qty: 10, unitCost: 50 }) await grnNew.submit() await expect(page).toHaveURL(/\/dashboard\/receiving\/grn\/\d+/) const grnDetail = new GrnDetailPage(page) await grnDetail.expectStatus("Draft") const before = await seeder.stockOnHand(item.itemId, warehouse.warehouseId) await grnDetail.confirm() await grnDetail.expectStatus("Confirmed") const after = await seeder.stockOnHand(item.itemId, warehouse.warehouseId) expect(after.onHand).toBeCloseTo(before.onHand + 10, 4) }) test("against-PO receipt pre-fills vendor/warehouse from the PO", async ({ page }) => { const po = await seeder.createPurchaseOrder({ vendorId: vendor.vendorId, warehouseId: warehouse.warehouseId, itemId: item.itemId, qty: 5, unitPrice: 40, }) const grnNew = new GrnNewPage(page) await grnNew.goto() await grnNew.useAgainstPo() await grnNew.selectPurchaseOrder(po.docNo) await grnNew.fillFirstLine({ item: item.name, qty: 5 }) await grnNew.submit() await expect(page).toHaveURL(/\/dashboard\/receiving\/grn\/\d+/) const grnDetail = new GrnDetailPage(page) await grnDetail.expectStatus("Draft") }) test("rejecting a Draft GRN's blocked submit (missing warehouse) keeps the user on the form", async ({ page }) => { const grnNew = new GrnNewPage(page) await grnNew.goto() await grnNew.useDirectReceipt() await grnNew.selectVendor(vendor.name) // Warehouse intentionally left unselected. await grnNew.fillFirstLine({ item: item.name, qty: 1, unitCost: 10 }) await grnNew.submit() await expect(page).toHaveURL(/\/dashboard\/receiving\/grn\/new/) }) test("releasing an on-hold line clears the hold and makes stock available", async ({ page }) => { const grn = await seeder.receiveStockOnHold({ warehouseId: warehouse.warehouseId, vendorId: vendor.vendorId, itemId: item.itemId, qty: 8, unitCost: 12, }) const grnDetail = new GrnDetailPage(page) await grnDetail.gotoById(grn.grnId) await grnDetail.expectStatus("Confirmed") const before = await seeder.stockOnHand(item.itemId, warehouse.warehouseId) expect(before.available).toBeLessThan(before.onHand) // held stock is on-hand but not available await grnDetail.releaseFirstOnHoldLine() const after = await seeder.stockOnHand(item.itemId, warehouse.warehouseId) expect(after.available).toBeCloseTo(before.available + 8, 4) }) test("rejecting an on-hold line surfaces a Create Return link", async ({ page }) => { const grn = await seeder.receiveStockOnHold({ warehouseId: warehouse.warehouseId, vendorId: vendor.vendorId, itemId: item.itemId, qty: 3, unitCost: 12, }) const grnDetail = new GrnDetailPage(page) await grnDetail.gotoById(grn.grnId) await grnDetail.expectStatus("Confirmed") await grnDetail.rejectFirstOnHoldLine() await grnDetail.expectCreateReturnLink() }) })