import { type Page } from "@playwright/test"; import { test, expect, STORAGE_LOCATION_NAME, ensureAvailableStock, createConfirmedSalesOrder, getOutboundShipmentDocNumber, type ConfirmedSalesOrder, } from "../fixtures/outbound-shipment-processing"; import { OutboundShipmentCreateFormPage } from "../pages/outbound-shipment-create-form"; import { OutboundShipmentDetailPage } from "../pages/outbound-shipment-detail"; import { OutboundShipmentListPage } from "../pages/outbound-shipment-list"; // Business flow: outbound-shipment-processing. // // An Inventory Manager issues goods out of stock against a confirmed sales // order. The two stories are each exercised as a happy path: // 1. Create Outbound Shipment — raise a DRAFT outbound shipment against a // CONFIRMED sales order from a chosen issue-from location (no stock moves). // 2. Settle Outbound Shipment — post the DRAFT shipment; AVAILABLE stock is // reduced and the shipment moves to POSTED. // // The seed ships neither sales orders nor stock, so each test first builds its // own CONFIRMED sales order (and, for the settle path, real AVAILABLE stock) via // the machine-user GraphQL client, then drives the outbound-shipment UI. const ORDERED_QUANTITY = 6; /** * Open the create form, select the given confirmed sales order + the seed * issue-from location, submit, and land on the new shipment's DRAFT detail. * Returns the created shipment's id (from the detail URL). */ async function createDraftOutboundShipment( page: Page, salesOrder: ConfirmedSalesOrder, ): Promise { const createForm = new OutboundShipmentCreateFormPage(page); await createForm.goto(); await expect(createForm.salesOrderSelect).toBeVisible(); await createForm.selectSalesOrderByName(salesOrder.docNumber); await createForm.selectIssueFromByName(STORAGE_LOCATION_NAME); // The open-lines preview confirms the picked order resolved to issuable lines. await expect(createForm.openLinesSection).toBeVisible(); await expect(createForm.openLineRow(0)).toBeVisible(); await createForm.submit(); // On submit the form routes straight to the new shipment's detail page. await page.waitForURL(/\/sales\/outbound-shipment\/[^/]+$/); const detail = new OutboundShipmentDetailPage(page); await expect(detail.heading).toBeVisible(); await expect(detail.statusBadge).toHaveText(/draft/i); const shipmentId = new URL(page.url()).pathname.split("/").pop() ?? ""; expect(shipmentId).not.toBe(""); return shipmentId; } test.describe("outbound-shipment-processing", () => { test("Inventory Manager creates a DRAFT outbound shipment against a confirmed sales order", async ({ page, }) => { const salesOrder = await createConfirmedSalesOrder(ORDERED_QUANTITY); const shipmentId = await createDraftOutboundShipment(page, salesOrder); const detail = new OutboundShipmentDetailPage(page); await expect(detail.informationCardTitle).toBeVisible(); await expect(detail.linesCardTitle).toBeVisible(); await expect(detail.itemColumnHeader).toBeVisible(); await expect(detail.storageLocationColumnHeader).toBeVisible(); await expect(detail.quantityColumnHeader).toBeVisible(); // A DRAFT shipment offers the Post/Settle action. await expect(detail.postButton).toBeVisible(); // The new shipment appears on the index, linked by its own document number. const docNumber = await getOutboundShipmentDocNumber(shipmentId); const list = new OutboundShipmentListPage(page); await list.goto(); await expect(list.heading).toBeVisible(); await expect(list.getShipmentLink(docNumber)).toBeVisible(); }); test("Inventory Manager settles a DRAFT outbound shipment, moving it to POSTED", async ({ page, }) => { // Real AVAILABLE stock at the issue-from location so the post can reduce it. await ensureAvailableStock(ORDERED_QUANTITY + 4); const salesOrder = await createConfirmedSalesOrder(ORDERED_QUANTITY); await createDraftOutboundShipment(page, salesOrder); const detail = new OutboundShipmentDetailPage(page); await detail.postButton.click(); // Posting reduces stock and moves the shipment to POSTED; the terminal // status drops the Post/Settle action. await expect(detail.statusBadge).toHaveText(/posted/i); await expect(detail.postButton).toBeHidden(); }); });