import { describe, expect, it, vi } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  GoodsReceiptNotDraftError,
  GoodsReceiptNotFoundError,
  InactiveExecutionReferenceError,
  NoOpenReceiptQuantityError,
  OverReceiptToleranceExceededError,
  PurchaseOrderNotReceivableError,
  SupersededLineReferencedError,
} from "../lib/errors.generated";
import {
  baseGoodsReceipt,
  baseGoodsReceiptLine,
  baseItem,
  basePurchaseOrderLine,
  orderedPurchaseOrder,
  postedGoodsReceipt,
} from "../testing/fixtures";
import {
  commandCtx,
  expectErr,
  expectOk,
  mockBPQueries,
  mockInventoryCommands,
  mockItemQueries,
} from "../testing/commandTestUtils";
import { run } from "./postGoodsReceipt";

const bpQ = mockBPQueries({ partner: { id: baseGoodsReceipt.supplierId, status: "ACTIVE" } });
const itemQ = mockItemQueries({ item: baseItem });
const invC = mockInventoryCommands();

describe("postGoodsReceipt", () => {
  it("posts a valid goods receipt and increases purchase-order received quantity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // 1. selectFrom GoodsReceipt
    spies.select.mockReturnValueOnce(baseGoodsReceipt);
    // 2. selectFrom GoodsReceiptLine -> execute (array)
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 3. selectFrom PurchaseOrderLine (by ids) -> execute (array)
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    // 4. selectFrom PurchaseOrder (by ids) -> execute (array)
    spies.select.mockReturnValueOnce([orderedPurchaseOrder]);
    // 5. selectFrom GoodsReceipt (status=POSTED) -> execute (array)
    spies.select.mockReturnValueOnce([]);
    // 6. selectFrom PurchaseOrderLine (by purchaseOrderId in poIds) -> allPoLinesForLineage
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    // 7. selectFrom GoodsReceiptLine (by purchaseOrderLineId in allPoLineIdsForLineage) -> execute (array)
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 8. updateTable GoodsReceipt (set status=POSTED)
    spies.update.mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" });
    // 9. selectFrom PurchaseOrderLine (all for PO) -> execute (array) for receiptStatus rollup
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    // 10. selectFrom GoodsReceiptLine (for PO lines) -> execute (array) for receiptStatus rollup
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 11. updateTable PurchaseOrder
    spies.update.mockReturnValueOnce({
      ...orderedPurchaseOrder,
      receiptStatus: "PARTIALLY_RECEIVED",
    });

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
  });

  it("posts PO-unlinked receipt lines without affecting any purchase order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const unlinkedLine = { ...baseGoodsReceiptLine, purchaseOrderLineId: null };
    // 1. selectFrom GoodsReceipt
    spies.select.mockReturnValueOnce(baseGoodsReceipt);
    // 2. selectFrom GoodsReceiptLine -> execute (array)
    spies.select.mockReturnValueOnce([unlinkedLine]);
    // No PO line/order lookups needed (poLineIds is empty)
    // 3. selectFrom GoodsReceipt (status=POSTED) -> always runs
    spies.select.mockReturnValueOnce([]);
    // 4. updateTable GoodsReceipt
    spies.update.mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" });

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
    // No PO update calls should have happened — only the GoodsReceipt status update
    expect(spies.set).toHaveBeenCalledTimes(1);
  });

  it("posts a consolidated receipt spanning multiple purchase orders and updates each PO independently", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const secondPO = { ...orderedPurchaseOrder, id: "purchase-order-4" };
    const secondPOLine = {
      ...basePurchaseOrderLine,
      id: "po-line-2",
      purchaseOrderId: secondPO.id,
    };
    const secondReceiptLine = {
      ...baseGoodsReceiptLine,
      id: "gr-line-2",
      purchaseOrderLineId: secondPOLine.id,
    };

    // 1. selectFrom GoodsReceipt
    spies.select.mockReturnValueOnce(baseGoodsReceipt);
    // 2. selectFrom GoodsReceiptLine -> execute (array, two lines)
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine, secondReceiptLine]);
    // 3. selectFrom PurchaseOrderLine (by ids) -> execute (array)
    spies.select.mockReturnValueOnce([basePurchaseOrderLine, secondPOLine]);
    // 4. selectFrom PurchaseOrder (by ids) -> execute (array)
    spies.select.mockReturnValueOnce([orderedPurchaseOrder, secondPO]);
    // 5. selectFrom GoodsReceipt (status=POSTED) -> execute (array)
    spies.select.mockReturnValueOnce([]);
    // 6. selectFrom PurchaseOrderLine (by purchaseOrderId in poIds) -> allPoLinesForLineage
    spies.select.mockReturnValueOnce([basePurchaseOrderLine, secondPOLine]);
    // 7. selectFrom GoodsReceiptLine (by purchaseOrderLineId in allPoLineIdsForLineage) -> execute (array)
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine, secondReceiptLine]);
    // 8. updateTable GoodsReceipt (set status=POSTED)
    spies.update.mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" });
    // 9. selectFrom PurchaseOrderLine (all for first PO) -> for receiptStatus rollup
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    // 10. selectFrom GoodsReceiptLine (for first PO lines) -> for receiptStatus rollup
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 11. updateTable PurchaseOrder (first PO)
    spies.update.mockReturnValueOnce({
      ...orderedPurchaseOrder,
      receiptStatus: "PARTIALLY_RECEIVED",
    });
    // 12. selectFrom PurchaseOrderLine (all for second PO) -> for receiptStatus rollup
    spies.select.mockReturnValueOnce([secondPOLine]);
    // 13. selectFrom GoodsReceiptLine (for second PO lines) -> for receiptStatus rollup
    spies.select.mockReturnValueOnce([secondReceiptLine]);
    // 14. updateTable PurchaseOrder (second PO)
    spies.update.mockReturnValueOnce({ ...secondPO, receiptStatus: "PARTIALLY_RECEIVED" });

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
  });

  it("transitions multiple purchase orders to different receipt statuses from a single receipt", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // First PO: partial receipt (4 of 10)
    // Second PO: full receipt (10 of 10)
    const secondPO = { ...orderedPurchaseOrder, id: "purchase-order-4" };
    const secondPOLine = {
      ...basePurchaseOrderLine,
      id: "po-line-2",
      purchaseOrderId: secondPO.id,
      quantity: 10,
    };
    const secondReceiptLine = {
      ...baseGoodsReceiptLine,
      id: "gr-line-2",
      purchaseOrderLineId: secondPOLine.id,
      quantity: 10,
    };

    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // GoodsReceipt
      .mockReturnValueOnce([baseGoodsReceiptLine, secondReceiptLine]) // GoodsReceiptLine
      .mockReturnValueOnce([basePurchaseOrderLine, secondPOLine]) // PurchaseOrderLine (by ids)
      .mockReturnValueOnce([orderedPurchaseOrder, secondPO]) // PurchaseOrder (by ids)
      .mockReturnValueOnce([]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([basePurchaseOrderLine, secondPOLine]) // allPoLinesForLineage
      .mockReturnValueOnce([baseGoodsReceiptLine, secondReceiptLine]) // GoodsReceiptLine (by allPoLineIdsForLineage)
      .mockReturnValueOnce([basePurchaseOrderLine]) // PO lines rollup for first PO
      .mockReturnValueOnce([baseGoodsReceiptLine]) // GoodsReceiptLine rollup for first PO
      .mockReturnValueOnce([secondPOLine]) // PO lines rollup for second PO
      .mockReturnValueOnce([secondReceiptLine]); // GoodsReceiptLine rollup for second PO
    spies.update
      .mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" }) // GoodsReceipt
      .mockReturnValueOnce({ ...orderedPurchaseOrder, receiptStatus: "PARTIALLY_RECEIVED" }) // First PO
      .mockReturnValueOnce({ ...secondPO, receiptStatus: "RECEIVED" }); // Second PO

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
  });

  it("transitions purchase order to partially received when open quantity remains", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt)
      .mockReturnValueOnce([baseGoodsReceiptLine])
      .mockReturnValueOnce([basePurchaseOrderLine])
      .mockReturnValueOnce([orderedPurchaseOrder])
      .mockReturnValueOnce([]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([basePurchaseOrderLine]) // allPoLinesForLineage
      .mockReturnValueOnce([baseGoodsReceiptLine]) // GoodsReceiptLine (by allPoLineIdsForLineage)
      .mockReturnValueOnce([basePurchaseOrderLine]) // rollup query
      .mockReturnValueOnce([baseGoodsReceiptLine]); // GoodsReceiptLine rollup
    spies.update
      .mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" })
      .mockReturnValueOnce({ ...orderedPurchaseOrder, receiptStatus: "PARTIALLY_RECEIVED" });

    await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(spies.set).toHaveBeenCalledWith(
      expect.objectContaining({ receiptStatus: "PARTIALLY_RECEIVED" }),
    );
  });

  it("transitions purchase order to received when all quantity is posted", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const receiptLine = { ...baseGoodsReceiptLine, quantity: 10 };
    const orderLine = { ...basePurchaseOrderLine, quantity: 10 };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt)
      .mockReturnValueOnce([receiptLine])
      .mockReturnValueOnce([orderLine])
      .mockReturnValueOnce([orderedPurchaseOrder])
      .mockReturnValueOnce([]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([orderLine]) // allPoLinesForLineage
      .mockReturnValueOnce([receiptLine]) // GoodsReceiptLine (by allPoLineIdsForLineage)
      .mockReturnValueOnce([orderLine]) // rollup query
      .mockReturnValueOnce([receiptLine]); // GoodsReceiptLine rollup
    spies.update
      .mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" })
      .mockReturnValueOnce({ ...orderedPurchaseOrder, receiptStatus: "RECEIVED" });

    await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(spies.set).toHaveBeenCalledWith(expect.objectContaining({ receiptStatus: "RECEIVED" }));
  });

  it("calls createStockMovement for stock-item receipt lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const mockInv = mockInventoryCommands();
    const createSpy = (mockInv.createStockMovement = vi.fn(mockInv.createStockMovement));

    spies.select
      .mockReturnValueOnce(baseGoodsReceipt)
      .mockReturnValueOnce([baseGoodsReceiptLine])
      .mockReturnValueOnce([basePurchaseOrderLine])
      .mockReturnValueOnce([orderedPurchaseOrder])
      .mockReturnValueOnce([]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([basePurchaseOrderLine]) // allPoLinesForLineage
      .mockReturnValueOnce([baseGoodsReceiptLine]) // GoodsReceiptLine (by allPoLineIdsForLineage)
      .mockReturnValueOnce([basePurchaseOrderLine]) // rollup query
      .mockReturnValueOnce([baseGoodsReceiptLine]); // GoodsReceiptLine rollup
    spies.update
      .mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" })
      .mockReturnValueOnce({ ...orderedPurchaseOrder, receiptStatus: "PARTIALLY_RECEIVED" });

    await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, mockInv);

    expect(createSpy).toHaveBeenCalledWith(
      db,
      expect.objectContaining({
        movementType: "GOODS_RECEIPT",
        lines: [
          expect.objectContaining({
            itemId: baseItem.id,
            quantity: Number(baseGoodsReceiptLine.quantity),
          }),
        ],
      }),
      commandCtx,
    );
  });

  it("skips createStockMovement for a service receipt", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const mockInv = mockInventoryCommands();
    const createSpy = (mockInv.createStockMovement = vi.fn(mockInv.createStockMovement));

    const serviceReceiptLine = { ...baseGoodsReceiptLine, requiresInventoryHandoff: false };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt)
      .mockReturnValueOnce([serviceReceiptLine])
      .mockReturnValueOnce([basePurchaseOrderLine])
      .mockReturnValueOnce([orderedPurchaseOrder])
      .mockReturnValueOnce([]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([basePurchaseOrderLine]) // allPoLinesForLineage
      .mockReturnValueOnce([serviceReceiptLine]) // GoodsReceiptLine (by allPoLineIdsForLineage)
      .mockReturnValueOnce([basePurchaseOrderLine]) // rollup query
      .mockReturnValueOnce([serviceReceiptLine]); // GoodsReceiptLine rollup
    spies.update
      .mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" })
      .mockReturnValueOnce({ ...orderedPurchaseOrder, receiptStatus: "PARTIALLY_RECEIVED" });

    await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, mockInv);

    expect(createSpy).not.toHaveBeenCalled();
  });

  it("returns error when goods receipt does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);
    expectErr(
      await run(db, { id: "missing-receipt" }, commandCtx, bpQ, itemQ, invC),
      GoodsReceiptNotFoundError,
    );
  });

  it("returns error when goods receipt is not draft", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(postedGoodsReceipt);
    expectErr(
      await run(db, { id: postedGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC),
      GoodsReceiptNotDraftError,
    );
  });

  it("returns error when posting exceeds tolerance", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const receiptLine = { ...baseGoodsReceiptLine, quantity: 11 };
    const orderLine = { ...basePurchaseOrderLine, quantity: 10 };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt)
      .mockReturnValueOnce([receiptLine])
      .mockReturnValueOnce([orderLine])
      .mockReturnValueOnce([orderedPurchaseOrder])
      .mockReturnValueOnce([]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([orderLine]) // allPoLinesForLineage
      .mockReturnValueOnce([receiptLine]); // GoodsReceiptLine (by allPoLineIdsForLineage) — receiptLine counted from this DRAFT GR won't be in postedGRIds, so alreadyReceived=0; openQty=10; 11 > 10
    expectErr(
      await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC),
      OverReceiptToleranceExceededError,
    );
  });

  it("returns error when posting a receipt line with no open quantity remaining", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const orderLine = { ...basePurchaseOrderLine, quantity: 10 };
    const receiptLine = { ...baseGoodsReceiptLine, quantity: 1 };
    // Existing posted receipt lines that sum to 10 (fully received)
    const existingReceiptLine = {
      ...baseGoodsReceiptLine,
      id: "gr-line-existing",
      goodsReceiptId: postedGoodsReceipt.id,
      quantity: 10,
    };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt)
      .mockReturnValueOnce([receiptLine])
      .mockReturnValueOnce([orderLine])
      .mockReturnValueOnce([orderedPurchaseOrder])
      .mockReturnValueOnce([postedGoodsReceipt]) // GoodsReceipt (status=POSTED) — one posted GR
      .mockReturnValueOnce([orderLine]) // allPoLinesForLineage
      .mockReturnValueOnce([existingReceiptLine, receiptLine]); // GoodsReceiptLine (by allPoLineIdsForLineage) — existingReceiptLine is from posted GR, so alreadyReceived=10; openQty=0

    expectErr(
      await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC),
      NoOpenReceiptQuantityError,
    );
  });

  it("returns error when any referenced purchase order is no longer receivable", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt)
      .mockReturnValueOnce([baseGoodsReceiptLine])
      .mockReturnValueOnce([basePurchaseOrderLine])
      .mockReturnValueOnce([{ ...orderedPurchaseOrder, orderStatus: "CANCELLED" }]);
    expectErr(
      await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC),
      PurchaseOrderNotReceivableError,
    );
  });

  it("returns error when supplier is inactive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseGoodsReceipt);
    expectErr(
      await run(
        db,
        { id: baseGoodsReceipt.id },
        commandCtx,
        mockBPQueries({ partner: { id: baseGoodsReceipt.supplierId, status: "INACTIVE" } }),
        itemQ,
        invC,
      ),
      InactiveExecutionReferenceError,
    );
  });

  it("posts successfully when item is inactive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const inactiveItemQ = mockItemQueries({ item: { ...baseItem, status: "INACTIVE" } });
    // 1. selectFrom GoodsReceipt
    spies.select.mockReturnValueOnce(baseGoodsReceipt);
    // 2. selectFrom GoodsReceiptLine -> execute (array)
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 3. selectFrom PurchaseOrderLine (by ids) -> execute (array)
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    // 4. selectFrom PurchaseOrder (by ids) -> execute (array)
    spies.select.mockReturnValueOnce([orderedPurchaseOrder]);
    // 5. selectFrom GoodsReceipt (status=POSTED) -> execute (array)
    spies.select.mockReturnValueOnce([]);
    // 6. selectFrom PurchaseOrderLine (by purchaseOrderId in poIds) -> allPoLinesForLineage
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    // 7. selectFrom GoodsReceiptLine (by purchaseOrderLineId in allPoLineIdsForLineage) -> execute (array)
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 8. updateTable GoodsReceipt (set status=POSTED)
    spies.update.mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" });
    // 9. selectFrom PurchaseOrderLine (all for PO) -> execute (array) for receiptStatus rollup
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    // 10. selectFrom GoodsReceiptLine (for PO lines) -> execute (array) for receiptStatus rollup
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 11. updateTable PurchaseOrder
    spies.update.mockReturnValueOnce({
      ...orderedPurchaseOrder,
      receiptStatus: "PARTIALLY_RECEIVED",
    });

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, inactiveItemQ, invC);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
  });

  it("validates over-receipt against the ACTIVE line's quantity using lineage-aware received quantity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // Superseded line with receipts already posted, and a new ACTIVE line
    const supersededLine = {
      ...basePurchaseOrderLine,
      id: "po-line-original",
      lineStatus: "SUPERSEDED",
      supersededByLineId: "po-line-revised",
      rootLineId: null,
    };
    const activeLine = {
      ...basePurchaseOrderLine,
      id: "po-line-revised",
      lineStatus: "ACTIVE",
      supersededByLineId: null,
      rootLineId: "po-line-original",
      quantity: "10",
    };
    // Receipt line for the current ACTIVE line
    const newReceiptLine = {
      ...baseGoodsReceiptLine,
      purchaseOrderLineId: "po-line-revised",
      quantity: "4",
    };
    // Existing posted receipt against superseded line (counts toward lineage total)
    const existingReceiptLine = {
      ...baseGoodsReceiptLine,
      id: "gr-line-existing",
      goodsReceiptId: postedGoodsReceipt.id,
      purchaseOrderLineId: "po-line-original",
      quantity: "4",
    };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // GoodsReceipt
      .mockReturnValueOnce([newReceiptLine]) // GoodsReceiptLine
      .mockReturnValueOnce([activeLine]) // PurchaseOrderLine (referenced by receipt)
      .mockReturnValueOnce([orderedPurchaseOrder]) // PurchaseOrder
      .mockReturnValueOnce([postedGoodsReceipt]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([supersededLine, activeLine]) // All PO lines for lineage
      .mockReturnValueOnce([existingReceiptLine, newReceiptLine]); // GoodsReceiptLine (all for lineage PO line IDs)
    // Post succeeds: lineage received = 4 (superseded) + 0 (active, only posted count) = 4; openQty = 10 - 4 = 6; 4 <= 6
    spies.update.mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" }); // GoodsReceipt
    // Rollup queries
    spies.select
      .mockReturnValueOnce([supersededLine, activeLine]) // PurchaseOrderLine (all for PO)
      .mockReturnValueOnce([existingReceiptLine, newReceiptLine]); // GoodsReceiptLine (rollup)
    spies.update.mockReturnValueOnce({
      ...orderedPurchaseOrder,
      receiptStatus: "PARTIALLY_RECEIVED",
    });

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
  });

  it("returns error when receipt line references a SUPERSEDED purchase-order line", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const supersededLine = {
      ...basePurchaseOrderLine,
      id: "po-line-superseded",
      lineStatus: "SUPERSEDED",
      supersededByLineId: "po-line-active",
      rootLineId: null,
    };
    const receiptLine = {
      ...baseGoodsReceiptLine,
      purchaseOrderLineId: "po-line-superseded",
    };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // GoodsReceipt
      .mockReturnValueOnce([receiptLine]) // GoodsReceiptLine
      .mockReturnValueOnce([supersededLine]) // PurchaseOrderLine (by ids)
      .mockReturnValueOnce([orderedPurchaseOrder]) // PurchaseOrder (by ids)
      .mockReturnValueOnce([]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([supersededLine]) // allPoLinesForLineage
      .mockReturnValueOnce([]); // GoodsReceiptLine (by allPoLineIdsForLineage)

    expectErr(
      await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC),
      SupersededLineReferencedError,
    );
  });

  it("receipts referencing superseded lines count toward lineage total for over-receipt validation", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // ACTIVE line with quantity 10; superseded line already received 10 → no open quantity
    const supersededLine = {
      ...basePurchaseOrderLine,
      id: "po-line-original",
      lineStatus: "SUPERSEDED",
      supersededByLineId: "po-line-revised",
      rootLineId: null,
    };
    const activeLine = {
      ...basePurchaseOrderLine,
      id: "po-line-revised",
      lineStatus: "ACTIVE",
      supersededByLineId: null,
      rootLineId: "po-line-original",
      quantity: "10",
    };
    const newReceiptLine = {
      ...baseGoodsReceiptLine,
      purchaseOrderLineId: "po-line-revised",
      quantity: "1",
    };
    // Full receipt already posted against superseded line
    const existingReceiptLine = {
      ...baseGoodsReceiptLine,
      id: "gr-line-existing",
      goodsReceiptId: postedGoodsReceipt.id,
      purchaseOrderLineId: "po-line-original",
      quantity: "10",
    };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // GoodsReceipt
      .mockReturnValueOnce([newReceiptLine]) // GoodsReceiptLine
      .mockReturnValueOnce([activeLine]) // PurchaseOrderLine (referenced by receipt)
      .mockReturnValueOnce([orderedPurchaseOrder]) // PurchaseOrder
      .mockReturnValueOnce([postedGoodsReceipt]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([supersededLine, activeLine]) // All PO lines for lineage
      .mockReturnValueOnce([existingReceiptLine, newReceiptLine]); // GoodsReceiptLine (all for lineage PO line IDs)
    // Lineage received = 10 (superseded); openQty = 10 - 10 = 0 → error

    expectErr(
      await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC),
      NoOpenReceiptQuantityError,
    );
  });
});
