Add repairs domain with tickets, line items, batches, and service templates

Full-stack implementation of instrument repair tracking: DB schema with
repair_ticket, repair_line_item, repair_batch, and repair_service_template
tables. Backend services and routes with pagination/search/sort. 20 API
tests covering CRUD, status workflow, line items, and batch operations.
Admin frontend with ticket list, detail with status progression, line item
management, batch list/detail with approval workflow, and new ticket form
with searchable account picker and intake photo uploads.
This commit is contained in:
Ryan Moon
2026-03-29 09:12:40 -05:00
parent 1d48f0befa
commit f17bbff02c
20 changed files with 2791 additions and 1 deletions

View File

@@ -16,10 +16,16 @@ import { Route as AuthenticatedUsersRouteImport } from './routes/_authenticated/
import { Route as AuthenticatedProfileRouteImport } from './routes/_authenticated/profile'
import { Route as AuthenticatedHelpRouteImport } from './routes/_authenticated/help'
import { Route as AuthenticatedRolesIndexRouteImport } from './routes/_authenticated/roles/index'
import { Route as AuthenticatedRepairsIndexRouteImport } from './routes/_authenticated/repairs/index'
import { Route as AuthenticatedRepairBatchesIndexRouteImport } from './routes/_authenticated/repair-batches/index'
import { Route as AuthenticatedMembersIndexRouteImport } from './routes/_authenticated/members/index'
import { Route as AuthenticatedAccountsIndexRouteImport } from './routes/_authenticated/accounts/index'
import { Route as AuthenticatedRolesNewRouteImport } from './routes/_authenticated/roles/new'
import { Route as AuthenticatedRolesRoleIdRouteImport } from './routes/_authenticated/roles/$roleId'
import { Route as AuthenticatedRepairsNewRouteImport } from './routes/_authenticated/repairs/new'
import { Route as AuthenticatedRepairsTicketIdRouteImport } from './routes/_authenticated/repairs/$ticketId'
import { Route as AuthenticatedRepairBatchesNewRouteImport } from './routes/_authenticated/repair-batches/new'
import { Route as AuthenticatedRepairBatchesBatchIdRouteImport } from './routes/_authenticated/repair-batches/$batchId'
import { Route as AuthenticatedMembersMemberIdRouteImport } from './routes/_authenticated/members/$memberId'
import { Route as AuthenticatedAccountsNewRouteImport } from './routes/_authenticated/accounts/new'
import { Route as AuthenticatedAccountsAccountIdRouteImport } from './routes/_authenticated/accounts/$accountId'
@@ -63,6 +69,18 @@ const AuthenticatedRolesIndexRoute = AuthenticatedRolesIndexRouteImport.update({
path: '/roles/',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedRepairsIndexRoute =
AuthenticatedRepairsIndexRouteImport.update({
id: '/repairs/',
path: '/repairs/',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedRepairBatchesIndexRoute =
AuthenticatedRepairBatchesIndexRouteImport.update({
id: '/repair-batches/',
path: '/repair-batches/',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedMembersIndexRoute =
AuthenticatedMembersIndexRouteImport.update({
id: '/members/',
@@ -86,6 +104,29 @@ const AuthenticatedRolesRoleIdRoute =
path: '/roles/$roleId',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedRepairsNewRoute = AuthenticatedRepairsNewRouteImport.update({
id: '/repairs/new',
path: '/repairs/new',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedRepairsTicketIdRoute =
AuthenticatedRepairsTicketIdRouteImport.update({
id: '/repairs/$ticketId',
path: '/repairs/$ticketId',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedRepairBatchesNewRoute =
AuthenticatedRepairBatchesNewRouteImport.update({
id: '/repair-batches/new',
path: '/repair-batches/new',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedRepairBatchesBatchIdRoute =
AuthenticatedRepairBatchesBatchIdRouteImport.update({
id: '/repair-batches/$batchId',
path: '/repair-batches/$batchId',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedMembersMemberIdRoute =
AuthenticatedMembersMemberIdRouteImport.update({
id: '/members/$memberId',
@@ -144,10 +185,16 @@ export interface FileRoutesByFullPath {
'/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren
'/accounts/new': typeof AuthenticatedAccountsNewRoute
'/members/$memberId': typeof AuthenticatedMembersMemberIdRoute
'/repair-batches/$batchId': typeof AuthenticatedRepairBatchesBatchIdRoute
'/repair-batches/new': typeof AuthenticatedRepairBatchesNewRoute
'/repairs/$ticketId': typeof AuthenticatedRepairsTicketIdRoute
'/repairs/new': typeof AuthenticatedRepairsNewRoute
'/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute
'/roles/new': typeof AuthenticatedRolesNewRoute
'/accounts/': typeof AuthenticatedAccountsIndexRoute
'/members/': typeof AuthenticatedMembersIndexRoute
'/repair-batches/': typeof AuthenticatedRepairBatchesIndexRoute
'/repairs/': typeof AuthenticatedRepairsIndexRoute
'/roles/': typeof AuthenticatedRolesIndexRoute
'/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute
'/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute
@@ -163,10 +210,16 @@ export interface FileRoutesByTo {
'/': typeof AuthenticatedIndexRoute
'/accounts/new': typeof AuthenticatedAccountsNewRoute
'/members/$memberId': typeof AuthenticatedMembersMemberIdRoute
'/repair-batches/$batchId': typeof AuthenticatedRepairBatchesBatchIdRoute
'/repair-batches/new': typeof AuthenticatedRepairBatchesNewRoute
'/repairs/$ticketId': typeof AuthenticatedRepairsTicketIdRoute
'/repairs/new': typeof AuthenticatedRepairsNewRoute
'/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute
'/roles/new': typeof AuthenticatedRolesNewRoute
'/accounts': typeof AuthenticatedAccountsIndexRoute
'/members': typeof AuthenticatedMembersIndexRoute
'/repair-batches': typeof AuthenticatedRepairBatchesIndexRoute
'/repairs': typeof AuthenticatedRepairsIndexRoute
'/roles': typeof AuthenticatedRolesIndexRoute
'/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute
'/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute
@@ -185,10 +238,16 @@ export interface FileRoutesById {
'/_authenticated/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren
'/_authenticated/accounts/new': typeof AuthenticatedAccountsNewRoute
'/_authenticated/members/$memberId': typeof AuthenticatedMembersMemberIdRoute
'/_authenticated/repair-batches/$batchId': typeof AuthenticatedRepairBatchesBatchIdRoute
'/_authenticated/repair-batches/new': typeof AuthenticatedRepairBatchesNewRoute
'/_authenticated/repairs/$ticketId': typeof AuthenticatedRepairsTicketIdRoute
'/_authenticated/repairs/new': typeof AuthenticatedRepairsNewRoute
'/_authenticated/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute
'/_authenticated/roles/new': typeof AuthenticatedRolesNewRoute
'/_authenticated/accounts/': typeof AuthenticatedAccountsIndexRoute
'/_authenticated/members/': typeof AuthenticatedMembersIndexRoute
'/_authenticated/repair-batches/': typeof AuthenticatedRepairBatchesIndexRoute
'/_authenticated/repairs/': typeof AuthenticatedRepairsIndexRoute
'/_authenticated/roles/': typeof AuthenticatedRolesIndexRoute
'/_authenticated/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute
'/_authenticated/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute
@@ -207,10 +266,16 @@ export interface FileRouteTypes {
| '/accounts/$accountId'
| '/accounts/new'
| '/members/$memberId'
| '/repair-batches/$batchId'
| '/repair-batches/new'
| '/repairs/$ticketId'
| '/repairs/new'
| '/roles/$roleId'
| '/roles/new'
| '/accounts/'
| '/members/'
| '/repair-batches/'
| '/repairs/'
| '/roles/'
| '/accounts/$accountId/members'
| '/accounts/$accountId/payment-methods'
@@ -226,10 +291,16 @@ export interface FileRouteTypes {
| '/'
| '/accounts/new'
| '/members/$memberId'
| '/repair-batches/$batchId'
| '/repair-batches/new'
| '/repairs/$ticketId'
| '/repairs/new'
| '/roles/$roleId'
| '/roles/new'
| '/accounts'
| '/members'
| '/repair-batches'
| '/repairs'
| '/roles'
| '/accounts/$accountId/members'
| '/accounts/$accountId/payment-methods'
@@ -247,10 +318,16 @@ export interface FileRouteTypes {
| '/_authenticated/accounts/$accountId'
| '/_authenticated/accounts/new'
| '/_authenticated/members/$memberId'
| '/_authenticated/repair-batches/$batchId'
| '/_authenticated/repair-batches/new'
| '/_authenticated/repairs/$ticketId'
| '/_authenticated/repairs/new'
| '/_authenticated/roles/$roleId'
| '/_authenticated/roles/new'
| '/_authenticated/accounts/'
| '/_authenticated/members/'
| '/_authenticated/repair-batches/'
| '/_authenticated/repairs/'
| '/_authenticated/roles/'
| '/_authenticated/accounts/$accountId/members'
| '/_authenticated/accounts/$accountId/payment-methods'
@@ -315,6 +392,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticatedRolesIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/repairs/': {
id: '/_authenticated/repairs/'
path: '/repairs'
fullPath: '/repairs/'
preLoaderRoute: typeof AuthenticatedRepairsIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/repair-batches/': {
id: '/_authenticated/repair-batches/'
path: '/repair-batches'
fullPath: '/repair-batches/'
preLoaderRoute: typeof AuthenticatedRepairBatchesIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/members/': {
id: '/_authenticated/members/'
path: '/members'
@@ -343,6 +434,34 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticatedRolesRoleIdRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/repairs/new': {
id: '/_authenticated/repairs/new'
path: '/repairs/new'
fullPath: '/repairs/new'
preLoaderRoute: typeof AuthenticatedRepairsNewRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/repairs/$ticketId': {
id: '/_authenticated/repairs/$ticketId'
path: '/repairs/$ticketId'
fullPath: '/repairs/$ticketId'
preLoaderRoute: typeof AuthenticatedRepairsTicketIdRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/repair-batches/new': {
id: '/_authenticated/repair-batches/new'
path: '/repair-batches/new'
fullPath: '/repair-batches/new'
preLoaderRoute: typeof AuthenticatedRepairBatchesNewRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/repair-batches/$batchId': {
id: '/_authenticated/repair-batches/$batchId'
path: '/repair-batches/$batchId'
fullPath: '/repair-batches/$batchId'
preLoaderRoute: typeof AuthenticatedRepairBatchesBatchIdRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/members/$memberId': {
id: '/_authenticated/members/$memberId'
path: '/members/$memberId'
@@ -437,10 +556,16 @@ interface AuthenticatedRouteChildren {
AuthenticatedAccountsAccountIdRoute: typeof AuthenticatedAccountsAccountIdRouteWithChildren
AuthenticatedAccountsNewRoute: typeof AuthenticatedAccountsNewRoute
AuthenticatedMembersMemberIdRoute: typeof AuthenticatedMembersMemberIdRoute
AuthenticatedRepairBatchesBatchIdRoute: typeof AuthenticatedRepairBatchesBatchIdRoute
AuthenticatedRepairBatchesNewRoute: typeof AuthenticatedRepairBatchesNewRoute
AuthenticatedRepairsTicketIdRoute: typeof AuthenticatedRepairsTicketIdRoute
AuthenticatedRepairsNewRoute: typeof AuthenticatedRepairsNewRoute
AuthenticatedRolesRoleIdRoute: typeof AuthenticatedRolesRoleIdRoute
AuthenticatedRolesNewRoute: typeof AuthenticatedRolesNewRoute
AuthenticatedAccountsIndexRoute: typeof AuthenticatedAccountsIndexRoute
AuthenticatedMembersIndexRoute: typeof AuthenticatedMembersIndexRoute
AuthenticatedRepairBatchesIndexRoute: typeof AuthenticatedRepairBatchesIndexRoute
AuthenticatedRepairsIndexRoute: typeof AuthenticatedRepairsIndexRoute
AuthenticatedRolesIndexRoute: typeof AuthenticatedRolesIndexRoute
}
@@ -453,10 +578,17 @@ const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
AuthenticatedAccountsAccountIdRouteWithChildren,
AuthenticatedAccountsNewRoute: AuthenticatedAccountsNewRoute,
AuthenticatedMembersMemberIdRoute: AuthenticatedMembersMemberIdRoute,
AuthenticatedRepairBatchesBatchIdRoute:
AuthenticatedRepairBatchesBatchIdRoute,
AuthenticatedRepairBatchesNewRoute: AuthenticatedRepairBatchesNewRoute,
AuthenticatedRepairsTicketIdRoute: AuthenticatedRepairsTicketIdRoute,
AuthenticatedRepairsNewRoute: AuthenticatedRepairsNewRoute,
AuthenticatedRolesRoleIdRoute: AuthenticatedRolesRoleIdRoute,
AuthenticatedRolesNewRoute: AuthenticatedRolesNewRoute,
AuthenticatedAccountsIndexRoute: AuthenticatedAccountsIndexRoute,
AuthenticatedMembersIndexRoute: AuthenticatedMembersIndexRoute,
AuthenticatedRepairBatchesIndexRoute: AuthenticatedRepairBatchesIndexRoute,
AuthenticatedRepairsIndexRoute: AuthenticatedRepairsIndexRoute,
AuthenticatedRolesIndexRoute: AuthenticatedRolesIndexRoute,
}