import { describe, it, expect } from 'bun:test' import { TransactionCreateSchema, TransactionLineItemCreateSchema, ApplyDiscountSchema, CompleteTransactionSchema, DiscountCreateSchema, DiscountUpdateSchema, DrawerOpenSchema, DrawerCloseSchema, TransactionType, TransactionStatus, PaymentMethod, DiscountType, DiscountAppliesTo, DrawerStatus, TaxCategory, } from '../../src/schemas/pos.schema.js' // ─── Enums ─────────────────────────────────────────────────────────────────── describe('POS enums', () => { it('TransactionType accepts valid values', () => { expect(TransactionType.parse('sale')).toBe('sale') expect(TransactionType.parse('repair_payment')).toBe('repair_payment') expect(TransactionType.parse('refund')).toBe('refund') }) it('TransactionType rejects invalid value', () => { expect(() => TransactionType.parse('layaway')).toThrow() }) it('TransactionStatus accepts valid values', () => { expect(TransactionStatus.parse('pending')).toBe('pending') expect(TransactionStatus.parse('voided')).toBe('voided') }) it('PaymentMethod accepts valid values', () => { expect(PaymentMethod.parse('cash')).toBe('cash') expect(PaymentMethod.parse('card_present')).toBe('card_present') expect(PaymentMethod.parse('account_charge')).toBe('account_charge') }) it('TaxCategory accepts valid values', () => { expect(TaxCategory.parse('goods')).toBe('goods') expect(TaxCategory.parse('service')).toBe('service') expect(TaxCategory.parse('exempt')).toBe('exempt') }) it('TaxCategory rejects invalid value', () => { expect(() => TaxCategory.parse('luxury')).toThrow() }) }) // ─── TransactionCreateSchema ───────────────────────────────────────────────── describe('TransactionCreateSchema', () => { it('parses minimal valid input', () => { const result = TransactionCreateSchema.parse({ transactionType: 'sale' }) expect(result.transactionType).toBe('sale') expect(result.taxExempt).toBe(false) }) it('parses full input with optional fields', () => { const result = TransactionCreateSchema.parse({ transactionType: 'repair_payment', locationId: '10000000-1000-4000-8000-000000000001', accountId: '10000000-1000-4000-8000-000000000002', taxExempt: true, taxExemptReason: 'Non-profit', notes: 'Customer walkup', }) expect(result.transactionType).toBe('repair_payment') expect(result.taxExempt).toBe(true) expect(result.taxExemptReason).toBe('Non-profit') }) it('rejects missing transactionType', () => { expect(() => TransactionCreateSchema.parse({})).toThrow() }) it('coerces empty string locationId to undefined', () => { const result = TransactionCreateSchema.parse({ transactionType: 'sale', locationId: '' }) expect(result.locationId).toBeUndefined() }) it('rejects invalid UUID for locationId', () => { expect(() => TransactionCreateSchema.parse({ transactionType: 'sale', locationId: 'not-a-uuid' }) ).toThrow() }) }) // ─── TransactionLineItemCreateSchema ───────────────────────────────────────── describe('TransactionLineItemCreateSchema', () => { it('parses valid line item', () => { const result = TransactionLineItemCreateSchema.parse({ description: 'Violin Strings', qty: 2, unitPrice: 12.99, }) expect(result.description).toBe('Violin Strings') expect(result.qty).toBe(2) expect(result.unitPrice).toBe(12.99) }) it('defaults qty to 1', () => { const result = TransactionLineItemCreateSchema.parse({ description: 'Capo', unitPrice: 19.99, }) expect(result.qty).toBe(1) }) it('coerces string unitPrice to number', () => { const result = TransactionLineItemCreateSchema.parse({ description: 'Pick', unitPrice: '5.99', }) expect(result.unitPrice).toBe(5.99) }) it('rejects empty description', () => { expect(() => TransactionLineItemCreateSchema.parse({ description: '', unitPrice: 10 }) ).toThrow() }) it('rejects description over 255 chars', () => { expect(() => TransactionLineItemCreateSchema.parse({ description: 'x'.repeat(256), unitPrice: 10 }) ).toThrow() }) it('rejects negative unitPrice', () => { expect(() => TransactionLineItemCreateSchema.parse({ description: 'Bad', unitPrice: -1 }) ).toThrow() }) it('rejects qty of 0', () => { expect(() => TransactionLineItemCreateSchema.parse({ description: 'Zero', qty: 0, unitPrice: 10 }) ).toThrow() }) it('rejects non-integer qty', () => { expect(() => TransactionLineItemCreateSchema.parse({ description: 'Frac', qty: 1.5, unitPrice: 10 }) ).toThrow() }) }) // ─── ApplyDiscountSchema ───────────────────────────────────────────────────── describe('ApplyDiscountSchema', () => { it('parses valid discount application', () => { const result = ApplyDiscountSchema.parse({ amount: 10, reason: 'Employee discount', lineItemId: '10000000-1000-4000-8000-000000000001', }) expect(result.amount).toBe(10) expect(result.reason).toBe('Employee discount') }) it('rejects missing reason', () => { expect(() => ApplyDiscountSchema.parse({ amount: 5 })).toThrow() }) it('rejects empty reason', () => { expect(() => ApplyDiscountSchema.parse({ amount: 5, reason: '' })).toThrow() }) it('rejects negative amount', () => { expect(() => ApplyDiscountSchema.parse({ amount: -1, reason: 'Nope' })).toThrow() }) it('allows zero amount', () => { const result = ApplyDiscountSchema.parse({ amount: 0, reason: 'Remove discount' }) expect(result.amount).toBe(0) }) }) // ─── CompleteTransactionSchema ─────────────────────────────────────────────── describe('CompleteTransactionSchema', () => { it('parses cash payment with amount tendered', () => { const result = CompleteTransactionSchema.parse({ paymentMethod: 'cash', amountTendered: 50, }) expect(result.paymentMethod).toBe('cash') expect(result.amountTendered).toBe(50) }) it('parses card payment without amount tendered', () => { const result = CompleteTransactionSchema.parse({ paymentMethod: 'card_present' }) expect(result.paymentMethod).toBe('card_present') expect(result.amountTendered).toBeUndefined() }) it('parses check payment with check number', () => { const result = CompleteTransactionSchema.parse({ paymentMethod: 'check', checkNumber: '1234', }) expect(result.checkNumber).toBe('1234') }) it('rejects missing paymentMethod', () => { expect(() => CompleteTransactionSchema.parse({})).toThrow() }) it('rejects invalid payment method', () => { expect(() => CompleteTransactionSchema.parse({ paymentMethod: 'bitcoin' })).toThrow() }) it('rejects check number over 50 chars', () => { expect(() => CompleteTransactionSchema.parse({ paymentMethod: 'check', checkNumber: 'x'.repeat(51) }) ).toThrow() }) }) // ─── DiscountCreateSchema ──────────────────────────────────────────────────── describe('DiscountCreateSchema', () => { it('parses valid discount', () => { const result = DiscountCreateSchema.parse({ name: '10% Off', discountType: 'percent', discountValue: 10, }) expect(result.name).toBe('10% Off') expect(result.appliesTo).toBe('line_item') // default expect(result.isActive).toBe(true) // default }) it('rejects missing name', () => { expect(() => DiscountCreateSchema.parse({ discountType: 'fixed', discountValue: 5 })).toThrow() }) it('rejects empty name', () => { expect(() => DiscountCreateSchema.parse({ name: '', discountType: 'fixed', discountValue: 5 }) ).toThrow() }) it('accepts order-level discount', () => { const result = DiscountCreateSchema.parse({ name: 'Order Disc', discountType: 'fixed', discountValue: 20, appliesTo: 'order', }) expect(result.appliesTo).toBe('order') }) it('accepts optional approval threshold', () => { const result = DiscountCreateSchema.parse({ name: 'Big Disc', discountType: 'percent', discountValue: 50, requiresApprovalAbove: 25, }) expect(result.requiresApprovalAbove).toBe(25) }) }) // ─── DiscountUpdateSchema ──────────────────────────────────────────────────── describe('DiscountUpdateSchema', () => { it('accepts partial update', () => { const result = DiscountUpdateSchema.parse({ name: 'Updated Name' }) expect(result.name).toBe('Updated Name') expect(result.discountType).toBeUndefined() }) it('accepts empty object (defaults still apply)', () => { const result = DiscountUpdateSchema.parse({}) expect(result.appliesTo).toBe('line_item') expect(result.isActive).toBe(true) expect(result.name).toBeUndefined() }) }) // ─── DrawerOpenSchema ──────────────────────────────────────────────────────── describe('DrawerOpenSchema', () => { it('parses valid drawer open', () => { const result = DrawerOpenSchema.parse({ locationId: '10000000-1000-4000-8000-000000000001', openingBalance: 200, }) expect(result.openingBalance).toBe(200) }) it('allows opening without location (floating drawer)', () => { const result = DrawerOpenSchema.parse({ openingBalance: 100 }) expect(result.locationId).toBeUndefined() }) it('rejects negative opening balance', () => { expect(() => DrawerOpenSchema.parse({ openingBalance: -50 })).toThrow() }) it('coerces string opening balance', () => { const result = DrawerOpenSchema.parse({ openingBalance: '150' }) expect(result.openingBalance).toBe(150) }) }) // ─── DrawerCloseSchema ─────────────────────────────────────────────────────── describe('DrawerCloseSchema', () => { it('parses valid drawer close', () => { const result = DrawerCloseSchema.parse({ closingBalance: 250 }) expect(result.closingBalance).toBe(250) }) it('accepts denominations', () => { const result = DrawerCloseSchema.parse({ closingBalance: 100, denominations: { ones: 20, fives: 10, tens: 3 }, }) expect(result.denominations!.ones).toBe(20) }) it('accepts notes', () => { const result = DrawerCloseSchema.parse({ closingBalance: 100, notes: 'Short $5' }) expect(result.notes).toBe('Short $5') }) it('coerces empty notes to undefined', () => { const result = DrawerCloseSchema.parse({ closingBalance: 100, notes: '' }) expect(result.notes).toBeUndefined() }) it('rejects negative closing balance', () => { expect(() => DrawerCloseSchema.parse({ closingBalance: -10 })).toThrow() }) })