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>
This commit is contained in:
84
packages/backend/src/services/register.service.ts
Normal file
84
packages/backend/src/services/register.service.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
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
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user