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>
85 lines
2.8 KiB
TypeScript
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
|
|
},
|
|
}
|