Files
lunarfront-app/packages/backend/src/services/register.service.ts
ryan 7d9aeaf188 feat: named registers, X/Z reports, daily rollup, fix drawerSessionId
Registers:
- New register table with location association
- CRUD service + API routes (POST/GET/PATCH/DELETE /registers)
- Drawer sessions now link to a register via registerId
- Register ID persisted in localStorage per device

X/Z Reports:
- ReportService with getDrawerReport() (X or Z depending on session state)
- Z report auto-displayed on drawer close in the drawer dialog
- X report (Current Shift Report) button on open drawer view
- Report shows: sales summary, payment breakdown, discounts, cash accountability, adjustments

Daily Rollup:
- ReportService.getDailyReport() aggregates all sessions at a location for a date
- New /reports/daily endpoint with locationId + date params
- Frontend daily report page with date picker, location selector, session breakdown

Critical Fix:
- drawerSessionId is now populated on transactions when completing (was never set before)
- This enables accurate per-drawer reporting and cash accountability

Migration 0044: register table, drawer_session.register_id column

Tests: 14 new (register CRUD, drawer report X/Z, drawerSessionId population, daily rollup, register-drawer link)
Full suite: 367 passed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:05:19 +00:00

85 lines
2.8 KiB
TypeScript

import { eq, and, count, type Column } from 'drizzle-orm'
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
import { registers } from '../db/schema/pos.js'
import { NotFoundError } from '../lib/errors.js'
import type { RegisterCreateInput, RegisterUpdateInput, PaginationInput } from '@lunarfront/shared/schemas'
import { withPagination, withSort, buildSearchCondition, paginatedResponse } from '../utils/pagination.js'
export const RegisterService = {
async create(db: PostgresJsDatabase<any>, input: RegisterCreateInput) {
const [register] = await db
.insert(registers)
.values({
locationId: input.locationId,
name: input.name,
})
.returning()
return register
},
async getById(db: PostgresJsDatabase<any>, id: string) {
const [register] = await db
.select()
.from(registers)
.where(eq(registers.id, id))
.limit(1)
return register ?? null
},
async list(db: PostgresJsDatabase<any>, params: PaginationInput, filters?: { locationId?: string }) {
const conditions = [eq(registers.isActive, true)]
if (params.q) {
conditions.push(buildSearchCondition(params.q, [registers.name])!)
}
if (filters?.locationId) {
conditions.push(eq(registers.locationId, filters.locationId))
}
const where = conditions.length === 1 ? conditions[0] : and(...conditions)
const sortableColumns: Record<string, Column> = {
name: registers.name,
created_at: registers.createdAt,
}
let query = db.select().from(registers).where(where).$dynamic()
query = withSort(query, params.sort, params.order, sortableColumns, registers.name)
query = withPagination(query, params.page, params.limit)
const [data, [{ total }]] = await Promise.all([
query,
db.select({ total: count() }).from(registers).where(where),
])
return paginatedResponse(data, total, params.page, params.limit)
},
async listAll(db: PostgresJsDatabase<any>, locationId?: string) {
const conditions = [eq(registers.isActive, true)]
if (locationId) conditions.push(eq(registers.locationId, locationId))
const where = conditions.length === 1 ? conditions[0] : and(...conditions)
return db.select().from(registers).where(where)
},
async update(db: PostgresJsDatabase<any>, id: string, input: RegisterUpdateInput) {
const [updated] = await db
.update(registers)
.set({ ...input, updatedAt: new Date() })
.where(eq(registers.id, id))
.returning()
if (!updated) throw new NotFoundError('Register')
return updated
},
async delete(db: PostgresJsDatabase<any>, id: string) {
const [deleted] = await db
.update(registers)
.set({ isActive: false, updatedAt: new Date() })
.where(eq(registers.id, id))
.returning()
if (!deleted) throw new NotFoundError('Register')
return deleted
},
}