import { eq } from 'drizzle-orm' import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js' import { locations } from '../db/schema/stores.js' import { AppError } from '../lib/errors.js' export type TaxCategory = 'goods' | 'service' | 'exempt' export const TaxService = { /** * Get the tax rate for a location, resolved by tax category: * - "goods" → location.taxRate (default) * - "service" → location.serviceTaxRate, falls back to taxRate * - "exempt" → 0 * * Returns 0 with no warning for exempt items. * Returns 0 with warning if no rate is configured on the location. */ async getRateForLocation( db: PostgresJsDatabase, locationId: string, taxCategory: TaxCategory = 'goods', ): Promise { if (taxCategory === 'exempt') return 0 const [location] = await db .select({ taxRate: locations.taxRate, serviceTaxRate: locations.serviceTaxRate }) .from(locations) .where(eq(locations.id, locationId)) .limit(1) if (!location) return 0 if (taxCategory === 'service') { // Use service rate if set, otherwise fall back to goods rate const rate = location.serviceTaxRate ?? location.taxRate return rate ? parseFloat(rate) : 0 } // Default: goods rate return location.taxRate ? parseFloat(location.taxRate) : 0 }, calculateTax(amount: number, rate: number): number { return Math.round(amount * rate * 100) / 100 }, /** * Swedish rounding: round to nearest $0.05 for cash payments. * Only affects the final total — tax and line items stay exact. */ roundToNickel(amount: number): number { return Math.round(amount * 20) / 20 }, /** * Map repair line item types to tax categories: * - "part" → goods (taxable) * - "labor" → service (may be taxed differently) * - "flat_rate" → goods (conservative — includes parts) * - "misc" → goods (default) */ repairItemTypeToTaxCategory(itemType: string): TaxCategory { switch (itemType) { case 'labor': return 'service' case 'consumable': return 'exempt' case 'part': case 'flat_rate': case 'misc': default: return 'goods' } }, // TODO: Integrate with a real tax rate API (TaxJar, Avalara, etc.) // Set TAX_API_KEY env var when ready. async lookupByZip( _zip: string, ): Promise<{ zip: string; rate: number; state_rate: number; county_rate: number; city_rate: number }> { if (!process.env.TAX_API_KEY) { throw new AppError('Tax rate lookup is not configured. Set TAX_API_KEY to enable automatic lookup.', 501) } // Placeholder — replace with real API call throw new AppError('Tax rate lookup not yet implemented', 501) }, }