import { suite } from '../lib/context.js'

const LOCATION_ID = '10000000-1000-4000-8000-000000000002'
const ROUNDING_LOCATION_ID = '10000000-1000-4000-8000-000000000003'

suite('POS', { tags: ['pos'] }, (t) => {
  // ─── Discounts (CRUD) ──────────────────────────────────────────────────────

  t.test('creates a discount', { tags: ['discounts', 'create'] }, async () => {
    const res = await t.api.post('/v1/discounts', {
      name: 'Employee 10%',
      discountType: 'percent',
      discountValue: 10,
      appliesTo: 'line_item',
    })
    t.assert.status(res, 201)
    t.assert.equal(res.data.name, 'Employee 10%')
    t.assert.equal(res.data.discountType, 'percent')
    t.assert.ok(res.data.id)
  })

  t.test('rejects discount without name', { tags: ['discounts', 'validation'] }, async () => {
    const res = await t.api.post('/v1/discounts', { discountType: 'fixed', discountValue: 5 })
    t.assert.status(res, 400)
  })

  t.test('lists discounts with pagination', { tags: ['discounts', 'list'] }, async () => {
    await t.api.post('/v1/discounts', { name: 'Disc A', discountType: 'fixed', discountValue: 5 })
    await t.api.post('/v1/discounts', { name: 'Disc B', discountType: 'percent', discountValue: 15 })
    const res = await t.api.get('/v1/discounts', { page: 1, limit: 25 })
    t.assert.status(res, 200)
    t.assert.ok(res.data.data.length >= 2)
    t.assert.ok(res.data.pagination)
  })

  t.test('lists all discounts (lookup)', { tags: ['discounts', 'list'] }, async () => {
    const res = await t.api.get('/v1/discounts/all')
    t.assert.status(res, 200)
    t.assert.ok(Array.isArray(res.data))
  })

  t.test('gets discount by id', { tags: ['discounts', 'read'] }, async () => {
    const created = await t.api.post('/v1/discounts', { name: 'Get Me', discountType: 'fixed', discountValue: 3 })
    const res = await t.api.get(`/v1/discounts/${created.data.id}`)
    t.assert.status(res, 200)
    t.assert.equal(res.data.name, 'Get Me')
  })

  t.test('updates a discount', { tags: ['discounts', 'update'] }, async () => {
    const created = await t.api.post('/v1/discounts', { name: 'Before', discountType: 'fixed', discountValue: 1 })
    const res = await t.api.patch(`/v1/discounts/${created.data.id}`, { name: 'After', discountValue: 99 })
    t.assert.status(res, 200)
    t.assert.equal(res.data.name, 'After')
  })

  t.test('soft-deletes a discount', { tags: ['discounts', 'delete'] }, async () => {
    const created = await t.api.post('/v1/discounts', { name: 'Delete Me', discountType: 'fixed', discountValue: 1 })
    const res = await t.api.del(`/v1/discounts/${created.data.id}`)
    t.assert.status(res, 200)
    t.assert.equal(res.data.isActive, false)
  })

  // ─── Drawer Sessions ───────────────────────────────────────────────────────

  t.test('opens a drawer session', { tags: ['drawer', 'create'] }, async () => {
    const res = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 200 })
    t.assert.status(res, 201)
    t.assert.equal(res.data.status, 'open')
    t.assert.ok(res.data.id)
    // Close it so future tests can open a new one
    await t.api.post(`/v1/drawer/${res.data.id}/close`, { closingBalance: 200 })
  })

  t.test('rejects opening second drawer at same location', { tags: ['drawer', 'validation'] }, async () => {
    const first = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
    t.assert.status(first, 201)

    const second = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
    t.assert.status(second, 409)

    // Cleanup
    await t.api.post(`/v1/drawer/${first.data.id}/close`, { closingBalance: 100 })
  })

  t.test('closes a drawer session with denominations', { tags: ['drawer', 'close'] }, async () => {
    const opened = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 150 })
    t.assert.status(opened, 201)

    const res = await t.api.post(`/v1/drawer/${opened.data.id}/close`, {
      closingBalance: 155,
      denominations: { ones: 50, fives: 20, tens: 5, twenties: 2 },
      notes: 'End of shift',
    })
    t.assert.status(res, 200)
    t.assert.equal(res.data.status, 'closed')
    t.assert.ok(res.data.closedAt)
  })

  t.test('gets current open drawer for location', { tags: ['drawer', 'read'] }, async () => {
    const opened = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
    t.assert.status(opened, 201)

    const res = await t.api.get('/v1/drawer/current', { locationId: LOCATION_ID })
    t.assert.status(res, 200)
    t.assert.equal(res.data.id, opened.data.id)

    // Cleanup
    await t.api.post(`/v1/drawer/${opened.data.id}/close`, { closingBalance: 100 })
  })

  t.test('lists drawer sessions with pagination', { tags: ['drawer', 'list'] }, async () => {
    const res = await t.api.get('/v1/drawer', { page: 1, limit: 25 })
    t.assert.status(res, 200)
    t.assert.ok(res.data.pagination)
  })

  // ─── Drawer Adjustments ─────────────────────────────────────────────────────

  t.test('adds cash out adjustment to open drawer', { tags: ['drawer', 'adjustments'] }, async () => {
    const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 200 })
    t.assert.status(drawer, 201)

    const res = await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, {
      type: 'cash_out',
      amount: 50,
      reason: 'Bank deposit',
    })
    t.assert.status(res, 201)
    t.assert.equal(res.data.type, 'cash_out')
    t.assert.equal(parseFloat(res.data.amount), 50)

    // Cleanup
    await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 150 })
  })

  t.test('adds cash in adjustment to open drawer', { tags: ['drawer', 'adjustments'] }, async () => {
    const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
    t.assert.status(drawer, 201)

    const res = await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, {
      type: 'cash_in',
      amount: 25,
      reason: 'Change from petty cash',
    })
    t.assert.status(res, 201)
    t.assert.equal(res.data.type, 'cash_in')

    // Cleanup
    await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 125 })
  })

  t.test('lists drawer adjustments', { tags: ['drawer', 'adjustments'] }, async () => {
    const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
    await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, { type: 'cash_out', amount: 30, reason: 'Test out' })
    await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, { type: 'cash_in', amount: 10, reason: 'Test in' })

    const res = await t.api.get(`/v1/drawer/${drawer.data.id}/adjustments`)
    t.assert.status(res, 200)
    t.assert.equal(res.data.data.length, 2)

    // Cleanup
    await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 80 })
  })

  t.test('drawer close includes adjustments in expected balance', { tags: ['drawer', 'adjustments', 'close'] }, async () => {
    const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 200 })
    t.assert.status(drawer, 201)

    // Cash out $50, cash in $20 → net adjustment = -$30
    await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, { type: 'cash_out', amount: 50, reason: 'Bank drop' })
    await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, { type: 'cash_in', amount: 20, reason: 'Extra change' })

    // Close — expected = 200 (opening) + 0 (no sales) + 20 (in) - 50 (out) = 170
    const closed = await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 170 })
    t.assert.status(closed, 200)
    t.assert.equal(parseFloat(closed.data.expectedBalance), 170)
    t.assert.equal(parseFloat(closed.data.overShort), 0)
  })

  t.test('rejects adjustment on closed drawer', { tags: ['drawer', 'adjustments', 'validation'] }, async () => {
    const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
    await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 100 })

    const res = await t.api.post(`/v1/drawer/${drawer.data.id}/adjustments`, {
      type: 'cash_out',
      amount: 10,
      reason: 'Should fail',
    })
    t.assert.status(res, 409)
  })

  // ─── Transactions ──────────────────────────────────────────────────────────

  t.test('creates a sale transaction', { tags: ['transactions', 'create'] }, async () => {
    const res = await t.api.post('/v1/transactions', {
      transactionType: 'sale',
      locationId: LOCATION_ID,
    })
    t.assert.status(res, 201)
    t.assert.equal(res.data.transactionType, 'sale')
    t.assert.equal(res.data.status, 'pending')
    t.assert.ok(res.data.transactionNumber)
    t.assert.contains(res.data.transactionNumber, 'TXN-')
  })

  t.test('rejects transaction without type', { tags: ['transactions', 'validation'] }, async () => {
    const res = await t.api.post('/v1/transactions', { locationId: LOCATION_ID })
    t.assert.status(res, 400)
  })

  t.test('adds line items and calculates tax', { tags: ['transactions', 'line-items', 'tax'] }, async () => {
    const txn = await t.api.post('/v1/transactions', {
      transactionType: 'sale',
      locationId: LOCATION_ID,
    })
    t.assert.status(txn, 201)

    // Add a line item (no product — ad hoc, taxed as goods at 8.25%)
    const item = await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Violin Strings',
      qty: 2,
      unitPrice: 12.99,
    })
    t.assert.status(item, 201)
    t.assert.equal(item.data.description, 'Violin Strings')
    t.assert.equal(item.data.qty, 2)

    // Verify tax was applied (8.25% on $25.98 = ~$2.14)
    const taxAmount = parseFloat(item.data.taxAmount)
    t.assert.greaterThan(taxAmount, 0)

    // Verify transaction totals were recalculated
    const updated = await t.api.get(`/v1/transactions/${txn.data.id}`)
    t.assert.status(updated, 200)
    const total = parseFloat(updated.data.total)
    t.assert.greaterThan(total, 0)
  })

  t.test('removes a line item and recalculates', { tags: ['transactions', 'line-items'] }, async () => {
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    const item1 = await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Item A',
      qty: 1,
      unitPrice: 10,
    })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Item B',
      qty: 1,
      unitPrice: 20,
    })

    // Remove item A
    const del = await t.api.del(`/v1/transactions/${txn.data.id}/line-items/${item1.data.id}`)
    t.assert.status(del, 200)

    // Transaction should only have item B's total
    const updated = await t.api.get(`/v1/transactions/${txn.data.id}`)
    const subtotal = parseFloat(updated.data.subtotal)
    t.assert.equal(subtotal, 20)
  })

  t.test('tax exempt transaction has zero tax', { tags: ['transactions', 'tax'] }, async () => {
    const txn = await t.api.post('/v1/transactions', {
      transactionType: 'sale',
      locationId: LOCATION_ID,
      taxExempt: true,
      taxExemptReason: 'Non-profit org',
    })
    t.assert.status(txn, 201)

    const item = await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Exempt Item',
      qty: 1,
      unitPrice: 100,
    })
    t.assert.status(item, 201)
    t.assert.equal(parseFloat(item.data.taxAmount), 0)

    const updated = await t.api.get(`/v1/transactions/${txn.data.id}`)
    t.assert.equal(parseFloat(updated.data.taxTotal), 0)
    t.assert.equal(parseFloat(updated.data.total), 100)
  })

  t.test('transaction without location has zero tax', { tags: ['transactions', 'tax'] }, async () => {
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale' })
    t.assert.status(txn, 201)

    const item = await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'No-location Item',
      qty: 1,
      unitPrice: 50,
    })
    t.assert.status(item, 201)
    t.assert.equal(parseFloat(item.data.taxAmount), 0)
  })

  // ─── Discounts on Transactions ─────────────────────────────────────────────

  t.test('applies a line-item discount', { tags: ['transactions', 'discounts'] }, async () => {
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    const item = await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Discountable Item',
      qty: 1,
      unitPrice: 100,
    })

    const res = await t.api.post(`/v1/transactions/${txn.data.id}/discounts`, {
      amount: 15,
      reason: 'Loyalty discount',
      lineItemId: item.data.id,
    })
    t.assert.status(res, 200)

    // Verify discount reduced the total
    const updated = await t.api.get(`/v1/transactions/${txn.data.id}`)
    const discountTotal = parseFloat(updated.data.discountTotal)
    t.assert.equal(discountTotal, 15)
    // Total should be (100 - 15) + tax on 85
    const total = parseFloat(updated.data.total)
    t.assert.greaterThan(total, 80)
  })

  t.test('rejects discount exceeding approval threshold', { tags: ['transactions', 'discounts', 'validation'] }, async () => {
    // Create a discount with approval threshold
    const disc = await t.api.post('/v1/discounts', {
      name: 'Threshold Disc',
      discountType: 'fixed',
      discountValue: 50,
      requiresApprovalAbove: 20,
    })

    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    const item = await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Expensive Item',
      qty: 1,
      unitPrice: 200,
    })

    // Try to apply $25 discount (above $20 threshold)
    const res = await t.api.post(`/v1/transactions/${txn.data.id}/discounts`, {
      discountId: disc.data.id,
      amount: 25,
      reason: 'Trying too much',
      lineItemId: item.data.id,
    })
    t.assert.status(res, 400)
  })

  // ─── Complete Transaction ──────────────────────────────────────────────────

  t.test('rejects completing transaction without open drawer', { tags: ['transactions', 'complete', 'validation', 'drawer'] }, async () => {
    // Ensure no drawer is open at LOCATION_ID
    const current = await t.api.get('/v1/drawer/current', { locationId: LOCATION_ID })
    if (current.status === 200 && current.data.id) {
      await t.api.post(`/v1/drawer/${current.data.id}/close`, { closingBalance: 0 })
    }

    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'No Drawer Item',
      qty: 1,
      unitPrice: 10,
    })

    const res = await t.api.post(`/v1/transactions/${txn.data.id}/complete`, {
      paymentMethod: 'cash',
      amountTendered: 20,
    })
    t.assert.status(res, 400)

    // Void to clean up
    await t.api.post(`/v1/transactions/${txn.data.id}/void`)
  })

  // Open a drawer for the remaining complete tests
  t.test('opens drawer for complete tests', { tags: ['transactions', 'complete', 'setup'] }, async () => {
    const res = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 200 })
    t.assert.status(res, 201)
  })

  t.test('completes a cash transaction with change', { tags: ['transactions', 'complete', 'cash'] }, async () => {
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Cash Sale Item',
      qty: 1,
      unitPrice: 10,
    })

    // Get updated total
    const pending = await t.api.get(`/v1/transactions/${txn.data.id}`)
    const total = parseFloat(pending.data.total)

    const res = await t.api.post(`/v1/transactions/${txn.data.id}/complete`, {
      paymentMethod: 'cash',
      amountTendered: 20,
    })
    t.assert.status(res, 200)
    t.assert.equal(res.data.status, 'completed')
    t.assert.equal(res.data.paymentMethod, 'cash')

    const changeGiven = parseFloat(res.data.changeGiven)
    // change = 20 - total
    const expectedChange = 20 - total
    t.assert.equal(changeGiven, expectedChange)
  })

  t.test('rejects cash payment with insufficient amount', { tags: ['transactions', 'complete', 'validation'] }, async () => {
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Underpay Item',
      qty: 1,
      unitPrice: 100,
    })

    const res = await t.api.post(`/v1/transactions/${txn.data.id}/complete`, {
      paymentMethod: 'cash',
      amountTendered: 5,
    })
    t.assert.status(res, 400)
  })

  t.test('completes a card transaction', { tags: ['transactions', 'complete'] }, async () => {
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Card Sale Item',
      qty: 1,
      unitPrice: 49.99,
    })

    const res = await t.api.post(`/v1/transactions/${txn.data.id}/complete`, {
      paymentMethod: 'card_present',
    })
    t.assert.status(res, 200)
    t.assert.equal(res.data.status, 'completed')
    t.assert.equal(res.data.paymentMethod, 'card_present')
  })

  t.test('rejects completing a non-pending transaction', { tags: ['transactions', 'complete', 'validation'] }, async () => {
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Double Complete',
      qty: 1,
      unitPrice: 10,
    })
    await t.api.post(`/v1/transactions/${txn.data.id}/complete`, {
      paymentMethod: 'card_present',
    })

    // Try completing again
    const res = await t.api.post(`/v1/transactions/${txn.data.id}/complete`, {
      paymentMethod: 'cash',
      amountTendered: 100,
    })
    t.assert.status(res, 409)
  })

  // ─── Void Transaction ──────────────────────────────────────────────────────

  t.test('voids a pending transaction', { tags: ['transactions', 'void'] }, async () => {
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Void Me',
      qty: 1,
      unitPrice: 25,
    })

    const res = await t.api.post(`/v1/transactions/${txn.data.id}/void`)
    t.assert.status(res, 200)
    t.assert.equal(res.data.status, 'voided')
  })

  t.test('rejects voiding a completed transaction', { tags: ['transactions', 'void', 'validation'] }, async () => {
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'No Void',
      qty: 1,
      unitPrice: 10,
    })
    await t.api.post(`/v1/transactions/${txn.data.id}/complete`, { paymentMethod: 'card_present' })

    const res = await t.api.post(`/v1/transactions/${txn.data.id}/void`)
    t.assert.status(res, 409)
  })

  // ─── Receipt ───────────────────────────────────────────────────────────────

  t.test('gets transaction receipt', { tags: ['transactions', 'receipt'] }, async () => {
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Receipt Item',
      qty: 1,
      unitPrice: 42,
    })
    await t.api.post(`/v1/transactions/${txn.data.id}/complete`, { paymentMethod: 'card_present' })

    const res = await t.api.get(`/v1/transactions/${txn.data.id}/receipt`)
    t.assert.status(res, 200)
    t.assert.ok(res.data.transaction)
    t.assert.ok(res.data.company)
    t.assert.ok(res.data.location)
    t.assert.equal(res.data.transaction.transactionNumber.startsWith('TXN-'), true)
  })

  // ─── List Transactions ─────────────────────────────────────────────────────

  t.test('lists transactions with pagination', { tags: ['transactions', 'list'] }, async () => {
    const res = await t.api.get('/v1/transactions', { page: 1, limit: 25 })
    t.assert.status(res, 200)
    t.assert.ok(res.data.data.length >= 1)
    t.assert.ok(res.data.pagination)
  })

  t.test('filters transactions by status', { tags: ['transactions', 'list', 'filter'] }, async () => {
    const res = await t.api.get('/v1/transactions', { status: 'completed' })
    t.assert.status(res, 200)
    for (const txn of res.data.data) {
      t.assert.equal(txn.status, 'completed')
    }
  })

  // ─── Tax Lookup (stub) ────────────────────────────────────────────────────

  t.test('tax lookup returns 501 (not configured)', { tags: ['tax'] }, async () => {
    const res = await t.api.get('/v1/tax/lookup/90210')
    t.assert.status(res, 501)
  })

  t.test('rejects invalid zip format', { tags: ['tax', 'validation'] }, async () => {
    const res = await t.api.get('/v1/tax/lookup/abc')
    t.assert.status(res, 400)
  })

  // ─── Cash Rounding ─────────────────────────────────────────────────────────

  // Close the LOCATION_ID drawer and open one at ROUNDING_LOCATION_ID
  t.test('setup drawer for rounding tests', { tags: ['transactions', 'rounding', 'setup'] }, async () => {
    // Close drawer at LOCATION_ID
    const current = await t.api.get('/v1/drawer/current', { locationId: LOCATION_ID })
    if (current.status === 200 && current.data.id) {
      await t.api.post(`/v1/drawer/${current.data.id}/close`, { closingBalance: 200 })
    }
    // Open drawer at ROUNDING_LOCATION_ID
    const res = await t.api.post('/v1/drawer/open', { locationId: ROUNDING_LOCATION_ID, openingBalance: 200 })
    t.assert.status(res, 201)
  })

  t.test('cash rounding adjusts total to nearest nickel', { tags: ['transactions', 'rounding'] }, async () => {
    // Create transaction at the rounding-enabled location
    const txn = await t.api.post('/v1/transactions', {
      transactionType: 'sale',
      locationId: ROUNDING_LOCATION_ID,
    })
    t.assert.status(txn, 201)

    // Add item that will produce a total not divisible by $0.05
    // $10.01 + 8.25% tax = $10.01 + $0.83 = $10.84 → rounds to $10.85
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Rounding Test Item',
      qty: 1,
      unitPrice: 10.01,
    })

    const pending = await t.api.get(`/v1/transactions/${txn.data.id}`)
    const exactTotal = parseFloat(pending.data.total)

    // Complete with cash — should round
    const res = await t.api.post(`/v1/transactions/${txn.data.id}/complete`, {
      paymentMethod: 'cash',
      amountTendered: 20,
    })
    t.assert.status(res, 200)

    const roundingAdj = parseFloat(res.data.roundingAdjustment)
    const changeGiven = parseFloat(res.data.changeGiven)
    const roundedTotal = exactTotal + roundingAdj

    // Rounded total should be divisible by 0.05
    t.assert.equal(Math.round(roundedTotal * 100) % 5, 0)
    // Change should be based on rounded total
    t.assert.equal(changeGiven, Math.round((20 - roundedTotal) * 100) / 100)
    // Adjustment should be small (-0.02 to +0.02)
    t.assert.ok(Math.abs(roundingAdj) <= 0.02)
  })

  t.test('card payment skips rounding even at rounding location', { tags: ['transactions', 'rounding'] }, async () => {
    const txn = await t.api.post('/v1/transactions', {
      transactionType: 'sale',
      locationId: ROUNDING_LOCATION_ID,
    })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Card Rounding Test',
      qty: 1,
      unitPrice: 10.01,
    })

    const res = await t.api.post(`/v1/transactions/${txn.data.id}/complete`, {
      paymentMethod: 'card_present',
    })
    t.assert.status(res, 200)
    t.assert.equal(parseFloat(res.data.roundingAdjustment), 0)
  })

  t.test('no rounding at non-rounding location', { tags: ['transactions', 'rounding'] }, async () => {
    // Open drawer at LOCATION_ID for this test
    const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
    t.assert.status(drawer, 201)

    const txn = await t.api.post('/v1/transactions', {
      transactionType: 'sale',
      locationId: LOCATION_ID,
    })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'No Rounding Item',
      qty: 1,
      unitPrice: 10.01,
    })

    const res = await t.api.post(`/v1/transactions/${txn.data.id}/complete`, {
      paymentMethod: 'cash',
      amountTendered: 20,
    })
    t.assert.status(res, 200)
    t.assert.equal(parseFloat(res.data.roundingAdjustment), 0)

    // Cleanup
    await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 100 })
  })

  // Close rounding location drawer
  t.test('cleanup rounding drawer', { tags: ['transactions', 'rounding', 'setup'] }, async () => {
    const current = await t.api.get('/v1/drawer/current', { locationId: ROUNDING_LOCATION_ID })
    if (current.status === 200 && current.data.id) {
      await t.api.post(`/v1/drawer/${current.data.id}/close`, { closingBalance: 200 })
    }
  })

  // ─── Full POS Flow ────────────────────────────────────────────────────────

  t.test('full sale flow: open drawer, sell, close drawer', { tags: ['e2e'] }, async () => {
    // 1. Open drawer
    const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
    t.assert.status(drawer, 201)

    // 2. Create transaction
    const txn = await t.api.post('/v1/transactions', {
      transactionType: 'sale',
      locationId: LOCATION_ID,
    })
    t.assert.status(txn, 201)

    // 3. Add line items
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Guitar Pick (12pk)',
      qty: 3,
      unitPrice: 5.99,
    })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, {
      description: 'Capo',
      qty: 1,
      unitPrice: 19.99,
    })

    // 4. Verify totals
    const pending = await t.api.get(`/v1/transactions/${txn.data.id}`)
    t.assert.status(pending, 200)
    const subtotal = parseFloat(pending.data.subtotal)
    const taxTotal = parseFloat(pending.data.taxTotal)
    const total = parseFloat(pending.data.total)
    // subtotal = 3*5.99 + 19.99 = 37.96
    t.assert.equal(subtotal, 37.96)
    t.assert.greaterThan(taxTotal, 0)
    t.assert.equal(total, subtotal + taxTotal)
    t.assert.equal(pending.data.lineItems.length, 2)

    // 5. Complete with cash
    const completed = await t.api.post(`/v1/transactions/${txn.data.id}/complete`, {
      paymentMethod: 'cash',
      amountTendered: 50,
    })
    t.assert.status(completed, 200)
    t.assert.equal(completed.data.status, 'completed')
    const change = parseFloat(completed.data.changeGiven)
    t.assert.equal(change, Math.round((50 - total) * 100) / 100)

    // 6. Get receipt
    const receipt = await t.api.get(`/v1/transactions/${txn.data.id}/receipt`)
    t.assert.status(receipt, 200)
    t.assert.equal(receipt.data.transaction.status, 'completed')

    // 7. Close drawer
    const closed = await t.api.post(`/v1/drawer/${drawer.data.id}/close`, {
      closingBalance: 100 + total - change,
      notes: 'End of day',
    })
    t.assert.status(closed, 200)
    t.assert.equal(closed.data.status, 'closed')
  })

  // ─── Repair → POS Integration ─────────────────────────────────────────────

  t.test('lists ready-for-pickup repair tickets', { tags: ['repair-pos', 'list'] }, async () => {
    // Create a repair ticket and move it to 'ready'
    const ticket = await t.api.post('/v1/repair-tickets', {
      customerName: 'POS Pickup Customer',
      customerPhone: '555-0100',
      problemDescription: 'Needs pickup test',
    })
    t.assert.status(ticket, 201)

    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'intake' })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'diagnosing' })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'in_progress' })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'ready' })

    const res = await t.api.get('/v1/repair-tickets/ready')
    t.assert.status(res, 200)
    t.assert.ok(res.data.data.length >= 1)
    const found = res.data.data.find((t: any) => t.id === ticket.data.id)
    t.assert.ok(found)
    t.assert.equal(found.status, 'ready')
  })

  t.test('searches ready tickets by customer name', { tags: ['repair-pos', 'search'] }, async () => {
    const res = await t.api.get('/v1/repair-tickets/ready', { q: 'POS Pickup' })
    t.assert.status(res, 200)
    t.assert.ok(res.data.data.length >= 1)
  })

  t.test('creates repair payment transaction from ticket', { tags: ['repair-pos', 'create'] }, async () => {
    // Create ticket with line items
    const ticket = await t.api.post('/v1/repair-tickets', {
      customerName: 'Repair Checkout Test',
      problemDescription: 'Full checkout flow',
      locationId: LOCATION_ID,
    })
    t.assert.status(ticket, 201)

    // Add line items — labor (service tax) + part (goods tax) + consumable (excluded)
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/line-items`, {
      itemType: 'labor',
      description: 'Diagnostic labor',
      qty: 1,
      unitPrice: 60,
      totalPrice: 60,
    })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/line-items`, {
      itemType: 'part',
      description: 'Replacement widget',
      qty: 2,
      unitPrice: 15,
      totalPrice: 30,
    })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/line-items`, {
      itemType: 'consumable',
      description: 'Shop supplies',
      qty: 1,
      unitPrice: 5,
      totalPrice: 5,
    })

    // Move to ready
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'intake' })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'diagnosing' })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'in_progress' })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'ready' })

    // Create POS transaction from repair
    const txn = await t.api.post(`/v1/transactions/from-repair/${ticket.data.id}`, {
      locationId: LOCATION_ID,
    })
    t.assert.status(txn, 201)
    t.assert.equal(txn.data.transactionType, 'repair_payment')
    t.assert.equal(txn.data.repairTicketId, ticket.data.id)

    // Should have 2 line items (consumable excluded)
    t.assert.equal(txn.data.lineItems.length, 2)

    // Subtotal should be labor ($60) + parts ($30) = $90
    const subtotal = parseFloat(txn.data.subtotal)
    t.assert.equal(subtotal, 90)

    // Tax should be > 0 (location has both goods and service rates)
    const taxTotal = parseFloat(txn.data.taxTotal)
    t.assert.greaterThan(taxTotal, 0)

    // Verify labor line item has service tax rate (5%)
    const laborItem = txn.data.lineItems.find((i: any) => i.description === 'Diagnostic labor')
    t.assert.ok(laborItem)
    t.assert.equal(parseFloat(laborItem.taxRate), 0.05)

    // Verify part line item has goods tax rate (8.25%)
    const partItem = txn.data.lineItems.find((i: any) => i.description === 'Replacement widget')
    t.assert.ok(partItem)
    t.assert.equal(parseFloat(partItem.taxRate), 0.0825)
  })

  t.test('rejects from-repair for non-ready ticket', { tags: ['repair-pos', 'validation'] }, async () => {
    const ticket = await t.api.post('/v1/repair-tickets', {
      customerName: 'Not Ready',
      problemDescription: 'Still in progress',
    })
    t.assert.status(ticket, 201)

    const res = await t.api.post(`/v1/transactions/from-repair/${ticket.data.id}`, {
      locationId: LOCATION_ID,
    })
    t.assert.status(res, 400)
  })

  t.test('rejects duplicate pending repair payment', { tags: ['repair-pos', 'validation'] }, async () => {
    // Create ready ticket with items
    const ticket = await t.api.post('/v1/repair-tickets', {
      customerName: 'Duplicate Test',
      problemDescription: 'Duplicate check',
      locationId: LOCATION_ID,
    })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/line-items`, {
      itemType: 'labor', description: 'Work', qty: 1, unitPrice: 50, totalPrice: 50,
    })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'intake' })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'diagnosing' })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'in_progress' })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'ready' })

    // First creation succeeds
    const first = await t.api.post(`/v1/transactions/from-repair/${ticket.data.id}`, { locationId: LOCATION_ID })
    t.assert.status(first, 201)

    // Second creation fails (pending transaction exists)
    const second = await t.api.post(`/v1/transactions/from-repair/${ticket.data.id}`, { locationId: LOCATION_ID })
    t.assert.status(second, 409)
  })

  t.test('completing repair payment marks ticket as picked_up', { tags: ['repair-pos', 'complete', 'e2e'] }, async () => {
    // Open drawer
    const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 200 })
    t.assert.status(drawer, 201)

    // Create ready ticket
    const ticket = await t.api.post('/v1/repair-tickets', {
      customerName: 'Pickup Complete Test',
      problemDescription: 'End to end',
      locationId: LOCATION_ID,
    })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/line-items`, {
      itemType: 'flat_rate', description: 'Service package', qty: 1, unitPrice: 100, totalPrice: 100,
    })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'intake' })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'diagnosing' })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'in_progress' })
    await t.api.post(`/v1/repair-tickets/${ticket.data.id}/status`, { status: 'ready' })

    // Create transaction from repair
    const txn = await t.api.post(`/v1/transactions/from-repair/${ticket.data.id}`, { locationId: LOCATION_ID })
    t.assert.status(txn, 201)

    // Complete payment
    const completed = await t.api.post(`/v1/transactions/${txn.data.id}/complete`, {
      paymentMethod: 'card_present',
    })
    t.assert.status(completed, 200)
    t.assert.equal(completed.data.status, 'completed')

    // Verify ticket was updated to picked_up
    const updatedTicket = await t.api.get(`/v1/repair-tickets/${ticket.data.id}`)
    t.assert.status(updatedTicket, 200)
    t.assert.equal(updatedTicket.data.status, 'picked_up')
    t.assert.ok(updatedTicket.data.completedDate)

    // Cleanup
    await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 200 })
  })

  // ─── Product isConsumable Filter ──────────────────────────────────────────

  t.test('isConsumable filter excludes consumables from product search', { tags: ['repair-pos', 'products'] }, async () => {
    // Create a consumable product
    const consumable = await t.api.post('/v1/products', {
      name: 'Test Shop Supply',
      isConsumable: true,
      price: 2.50,
    })
    t.assert.status(consumable, 201)

    // Create a normal product
    const normal = await t.api.post('/v1/products', {
      name: 'Test Normal Product',
      isConsumable: false,
      price: 25,
    })
    t.assert.status(normal, 201)

    // Search with isConsumable=false should exclude the consumable
    const res = await t.api.get('/v1/products', { q: 'Test', isConsumable: 'false' })
    t.assert.status(res, 200)
    const ids = res.data.data.map((p: any) => p.id)
    t.assert.ok(!ids.includes(consumable.data.id))
    t.assert.ok(ids.includes(normal.data.id))

    // Search with isConsumable=true should only show consumable
    const res2 = await t.api.get('/v1/products', { q: 'Test Shop Supply', isConsumable: 'true' })
    t.assert.status(res2, 200)
    t.assert.ok(res2.data.data.some((p: any) => p.id === consumable.data.id))
  })

  // ─── Registers ────────────────────────────────────────────────────────────

  t.test('creates a register', { tags: ['registers', 'create'] }, async () => {
    const res = await t.api.post('/v1/registers', { locationId: LOCATION_ID, name: 'Register 1' })
    t.assert.status(res, 201)
    t.assert.equal(res.data.name, 'Register 1')
    t.assert.equal(res.data.locationId, LOCATION_ID)
    t.assert.equal(res.data.isActive, true)
  })

  t.test('lists registers for a location', { tags: ['registers', 'list'] }, async () => {
    await t.api.post('/v1/registers', { locationId: LOCATION_ID, name: 'Register 2' })
    const res = await t.api.get('/v1/registers', { locationId: LOCATION_ID })
    t.assert.status(res, 200)
    t.assert.ok(res.data.data.length >= 2)
    t.assert.ok(res.data.pagination)
  })

  t.test('lists all registers (lookup)', { tags: ['registers', 'list'] }, async () => {
    const res = await t.api.get('/v1/registers/all', { locationId: LOCATION_ID })
    t.assert.status(res, 200)
    t.assert.ok(res.data.data.length >= 2)
  })

  t.test('updates a register name', { tags: ['registers', 'update'] }, async () => {
    const created = await t.api.post('/v1/registers', { locationId: LOCATION_ID, name: 'Old Name' })
    const res = await t.api.patch(`/v1/registers/${created.data.id}`, { name: 'New Name' })
    t.assert.status(res, 200)
    t.assert.equal(res.data.name, 'New Name')
  })

  t.test('deactivates a register', { tags: ['registers', 'delete'] }, async () => {
    const created = await t.api.post('/v1/registers', { locationId: LOCATION_ID, name: 'Delete Me' })
    const res = await t.api.del(`/v1/registers/${created.data.id}`)
    t.assert.status(res, 200)
    t.assert.equal(res.data.isActive, false)
  })

  // ─── Drawer Reports (X/Z) ────────────────────────────────────────────────

  t.test('cleanup: close any open drawers for report tests', { tags: ['reports', 'setup'] }, async () => {
    const current = await t.api.get('/v1/drawer/current', { locationId: LOCATION_ID })
    if (current.status === 200 && current.data?.id) {
      await t.api.post(`/v1/drawer/${current.data.id}/close`, { closingBalance: 0 })
    }
  })

  t.test('drawer report returns correct data for a session with transactions', { tags: ['reports', 'drawer'] }, async () => {
    // Open drawer
    const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
    t.assert.status(drawer, 201)

    // Make a cash sale
    const txn1 = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn1.data.id}/line-items`, { description: 'Report Item 1', qty: 1, unitPrice: 50 })
    await t.api.post(`/v1/transactions/${txn1.data.id}/complete`, { paymentMethod: 'cash', amountTendered: 60 })

    // Make a card sale
    const txn2 = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn2.data.id}/line-items`, { description: 'Report Item 2', qty: 1, unitPrice: 30 })
    await t.api.post(`/v1/transactions/${txn2.data.id}/complete`, { paymentMethod: 'card_present' })

    // Void a transaction
    const txn3 = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn3.data.id}/line-items`, { description: 'Voided Item', qty: 1, unitPrice: 10 })
    await t.api.post(`/v1/transactions/${txn3.data.id}/void`)

    // Get X report (drawer still open)
    const xReport = await t.api.get(`/v1/reports/drawer/${drawer.data.id}`)
    t.assert.status(xReport, 200)
    t.assert.equal(xReport.data.sales.transactionCount, 2)
    t.assert.greaterThan(xReport.data.sales.grossSales, 0)
    // Voided transactions don't go through complete() so drawerSessionId isn't set
    // They won't appear in the drawer report — this is correct behavior
    t.assert.ok(xReport.data.payments.cash)
    t.assert.ok(xReport.data.payments.card_present)
    t.assert.equal(xReport.data.cash.actualBalance, null) // not closed yet

    // Close drawer
    const closingAmount = 100 + xReport.data.cash.cashSales
    await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: closingAmount })

    // Get Z report (drawer closed)
    const zReport = await t.api.get(`/v1/reports/drawer/${drawer.data.id}`)
    t.assert.status(zReport, 200)
    t.assert.ok(zReport.data.session.closedAt)
    t.assert.ok(zReport.data.cash.actualBalance !== null)
    t.assert.ok(typeof zReport.data.cash.overShort === 'number')
  })

  t.test('drawerSessionId is populated on completed transactions', { tags: ['reports', 'drawer-session-id'] }, async () => {
    // Cleanup any open drawer
    const cur = await t.api.get('/v1/drawer/current', { locationId: LOCATION_ID })
    if (cur.status === 200 && cur.data?.id) await t.api.post(`/v1/drawer/${cur.data.id}/close`, { closingBalance: 0 })

    const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, openingBalance: 100 })
    t.assert.status(drawer, 201)

    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, { description: 'Session ID Test', qty: 1, unitPrice: 20 })
    await t.api.post(`/v1/transactions/${txn.data.id}/complete`, { paymentMethod: 'card_present' })

    const completed = await t.api.get(`/v1/transactions/${txn.data.id}`)
    t.assert.status(completed, 200)
    t.assert.equal(completed.data.drawerSessionId, drawer.data.id)

    await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 100 })
  })

  // ─── Daily Report ─────────────────────────────────────────────────────────

  t.test('daily report aggregates across sessions', { tags: ['reports', 'daily'] }, async () => {
    const today = new Date().toISOString().slice(0, 10)
    const res = await t.api.get('/v1/reports/daily', { locationId: LOCATION_ID, date: today })
    t.assert.status(res, 200)
    t.assert.equal(res.data.date, today)
    t.assert.ok(res.data.location)
    t.assert.ok(Array.isArray(res.data.sessions))
    t.assert.ok(typeof res.data.sales.grossSales === 'number')
    t.assert.ok(typeof res.data.payments === 'object')
    t.assert.ok(typeof res.data.cash.totalExpected === 'number')
  })

  t.test('daily report rejects missing params', { tags: ['reports', 'daily', 'validation'] }, async () => {
    const res = await t.api.get('/v1/reports/daily', {})
    t.assert.status(res, 400)
  })

  t.test('opens drawer with register', { tags: ['registers', 'drawer'] }, async () => {
    // Cleanup any open drawer
    const cur = await t.api.get('/v1/drawer/current', { locationId: LOCATION_ID })
    if (cur.status === 200 && cur.data?.id) await t.api.post(`/v1/drawer/${cur.data.id}/close`, { closingBalance: 0 })

    const reg = await t.api.post('/v1/registers', { locationId: LOCATION_ID, name: 'Report Register' })
    t.assert.status(reg, 201)

    const drawer = await t.api.post('/v1/drawer/open', { locationId: LOCATION_ID, registerId: reg.data.id, openingBalance: 100 })
    t.assert.status(drawer, 201)

    // Get report to check register info
    const report = await t.api.get(`/v1/reports/drawer/${drawer.data.id}`)
    t.assert.status(report, 200)
    t.assert.ok(report.data.session.register)
    t.assert.equal(report.data.session.register.name, 'Report Register')

    // Cleanup
    await t.api.post(`/v1/drawer/${drawer.data.id}/close`, { closingBalance: 100 })
  })

  // ─── Email Receipt ──────────────────────────────────────────────────────────

  t.test('emails a receipt for a completed transaction', { tags: ['transactions', 'email'] }, async () => {
    // Create and complete a transaction
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, { description: 'Email Test Item', qty: 1, unitPrice: 25 })
    await t.api.post(`/v1/transactions/${txn.data.id}/complete`, { paymentMethod: 'card_present' })

    const res = await t.api.post(`/v1/transactions/${txn.data.id}/email-receipt`, { email: 'customer@test.com' })
    t.assert.status(res, 200)
    t.assert.equal(res.data.message, 'Receipt sent')
    t.assert.equal(res.data.sentTo, 'customer@test.com')
  })

  t.test('rejects email receipt with invalid email', { tags: ['transactions', 'email', 'validation'] }, async () => {
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, { description: 'Bad Email Item', qty: 1, unitPrice: 10 })
    await t.api.post(`/v1/transactions/${txn.data.id}/complete`, { paymentMethod: 'card_present' })

    const res = await t.api.post(`/v1/transactions/${txn.data.id}/email-receipt`, { email: 'not-an-email' })
    t.assert.status(res, 400)
  })

  t.test('rejects email receipt with missing body', { tags: ['transactions', 'email', 'validation'] }, async () => {
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, { description: 'No Body Item', qty: 1, unitPrice: 10 })
    await t.api.post(`/v1/transactions/${txn.data.id}/complete`, { paymentMethod: 'card_present' })

    const res = await t.api.post(`/v1/transactions/${txn.data.id}/email-receipt`, {})
    t.assert.status(res, 400)
  })

  t.test('receipt response includes customerEmail', { tags: ['transactions', 'email'] }, async () => {
    // Create account with email
    const acct = await t.api.post('/v1/accounts', { name: 'Email Customer', email: 'acct@test.com', billingMode: 'consolidated' })
    const txn = await t.api.post('/v1/transactions', { transactionType: 'sale', locationId: LOCATION_ID, accountId: acct.data.id })
    await t.api.post(`/v1/transactions/${txn.data.id}/line-items`, { description: 'Account Item', qty: 1, unitPrice: 50 })
    await t.api.post(`/v1/transactions/${txn.data.id}/complete`, { paymentMethod: 'card_present' })

    const receipt = await t.api.get(`/v1/transactions/${txn.data.id}/receipt`)
    t.assert.status(receipt, 200)
    t.assert.equal(receipt.data.customerEmail, 'acct@test.com')
  })
})
