Build inventory frontend and stock management features

- Full inventory UI: product list with search/filter, product detail with
  tabs (details, units, suppliers, stock receipts, price history)
- Product filters: category, type (serialized/rental/repair), low stock,
  active/inactive — all server-side with URL-synced state
- Product-supplier junction: link products to multiple suppliers with
  preferred flag, joined supplier details in UI
- Stock receipts: record incoming stock with supplier, qty, cost per unit,
  invoice number; auto-increments qty_on_hand for non-serialized products
- Price history tab on product detail page
- categories/all endpoint to avoid pagination limit on dropdown fetches
- categoryId filter on product list endpoint
- Repair parts and additional inventory items in music store seed data
- isDualUseRepair corrected: instruments set to false, strings/parts true
- Product-supplier links and stock receipts in seed data
- Price history seed data simulating cost increases over past year
- 37 API tests covering categories, suppliers, products, units,
  product-suppliers, and stock receipts
- alert-dialog and checkbox UI components
- sync-and-deploy.sh script for rsync + remote deploy
This commit is contained in:
Ryan Moon
2026-03-30 20:12:07 -05:00
parent ec09e319ed
commit 5f5ba9e4a2
24 changed files with 4023 additions and 187 deletions

View File

@@ -21,6 +21,7 @@ import { Route as AuthenticatedRolesIndexRouteImport } from './routes/_authentic
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 AuthenticatedInventoryIndexRouteImport } from './routes/_authenticated/inventory/index'
import { Route as AuthenticatedFilesIndexRouteImport } from './routes/_authenticated/files/index'
import { Route as AuthenticatedAccountsIndexRouteImport } from './routes/_authenticated/accounts/index'
import { Route as AuthenticatedRolesNewRouteImport } from './routes/_authenticated/roles/new'
@@ -31,6 +32,8 @@ import { Route as AuthenticatedRepairsTicketIdRouteImport } from './routes/_auth
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 AuthenticatedInventoryCategoriesRouteImport } from './routes/_authenticated/inventory/categories'
import { Route as AuthenticatedInventoryProductIdRouteImport } from './routes/_authenticated/inventory/$productId'
import { Route as AuthenticatedAccountsNewRouteImport } from './routes/_authenticated/accounts/new'
import { Route as AuthenticatedAccountsAccountIdRouteImport } from './routes/_authenticated/accounts/$accountId'
import { Route as AuthenticatedLessonsTemplatesIndexRouteImport } from './routes/_authenticated/lessons/templates/index'
@@ -38,6 +41,7 @@ import { Route as AuthenticatedLessonsSessionsIndexRouteImport } from './routes/
import { Route as AuthenticatedLessonsScheduleIndexRouteImport } from './routes/_authenticated/lessons/schedule/index'
import { Route as AuthenticatedLessonsPlansIndexRouteImport } from './routes/_authenticated/lessons/plans/index'
import { Route as AuthenticatedLessonsEnrollmentsIndexRouteImport } from './routes/_authenticated/lessons/enrollments/index'
import { Route as AuthenticatedInventorySuppliersIndexRouteImport } from './routes/_authenticated/inventory/suppliers/index'
import { Route as AuthenticatedAccountsAccountIdIndexRouteImport } from './routes/_authenticated/accounts/$accountId/index'
import { Route as AuthenticatedLessonsTemplatesNewRouteImport } from './routes/_authenticated/lessons/templates/new'
import { Route as AuthenticatedLessonsTemplatesTemplateIdRouteImport } from './routes/_authenticated/lessons/templates/$templateId'
@@ -114,6 +118,12 @@ const AuthenticatedMembersIndexRoute =
path: '/members/',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedInventoryIndexRoute =
AuthenticatedInventoryIndexRouteImport.update({
id: '/inventory/',
path: '/inventory/',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedFilesIndexRoute = AuthenticatedFilesIndexRouteImport.update({
id: '/files/',
path: '/files/',
@@ -171,6 +181,18 @@ const AuthenticatedMembersMemberIdRoute =
path: '/members/$memberId',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedInventoryCategoriesRoute =
AuthenticatedInventoryCategoriesRouteImport.update({
id: '/inventory/categories',
path: '/inventory/categories',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedInventoryProductIdRoute =
AuthenticatedInventoryProductIdRouteImport.update({
id: '/inventory/$productId',
path: '/inventory/$productId',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedAccountsNewRoute =
AuthenticatedAccountsNewRouteImport.update({
id: '/accounts/new',
@@ -213,6 +235,12 @@ const AuthenticatedLessonsEnrollmentsIndexRoute =
path: '/lessons/enrollments/',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedInventorySuppliersIndexRoute =
AuthenticatedInventorySuppliersIndexRouteImport.update({
id: '/inventory/suppliers/',
path: '/inventory/suppliers/',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedAccountsAccountIdIndexRoute =
AuthenticatedAccountsAccountIdIndexRouteImport.update({
id: '/',
@@ -301,6 +329,8 @@ export interface FileRoutesByFullPath {
'/users': typeof AuthenticatedUsersRoute
'/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren
'/accounts/new': typeof AuthenticatedAccountsNewRoute
'/inventory/$productId': typeof AuthenticatedInventoryProductIdRoute
'/inventory/categories': typeof AuthenticatedInventoryCategoriesRoute
'/members/$memberId': typeof AuthenticatedMembersMemberIdRoute
'/repair-batches/$batchId': typeof AuthenticatedRepairBatchesBatchIdRoute
'/repair-batches/new': typeof AuthenticatedRepairBatchesNewRoute
@@ -311,6 +341,7 @@ export interface FileRoutesByFullPath {
'/roles/new': typeof AuthenticatedRolesNewRoute
'/accounts/': typeof AuthenticatedAccountsIndexRoute
'/files/': typeof AuthenticatedFilesIndexRoute
'/inventory/': typeof AuthenticatedInventoryIndexRoute
'/members/': typeof AuthenticatedMembersIndexRoute
'/repair-batches/': typeof AuthenticatedRepairBatchesIndexRoute
'/repairs/': typeof AuthenticatedRepairsIndexRoute
@@ -328,6 +359,7 @@ export interface FileRoutesByFullPath {
'/lessons/templates/$templateId': typeof AuthenticatedLessonsTemplatesTemplateIdRoute
'/lessons/templates/new': typeof AuthenticatedLessonsTemplatesNewRoute
'/accounts/$accountId/': typeof AuthenticatedAccountsAccountIdIndexRoute
'/inventory/suppliers/': typeof AuthenticatedInventorySuppliersIndexRoute
'/lessons/enrollments/': typeof AuthenticatedLessonsEnrollmentsIndexRoute
'/lessons/plans/': typeof AuthenticatedLessonsPlansIndexRoute
'/lessons/schedule/': typeof AuthenticatedLessonsScheduleIndexRoute
@@ -343,6 +375,8 @@ export interface FileRoutesByTo {
'/users': typeof AuthenticatedUsersRoute
'/': typeof AuthenticatedIndexRoute
'/accounts/new': typeof AuthenticatedAccountsNewRoute
'/inventory/$productId': typeof AuthenticatedInventoryProductIdRoute
'/inventory/categories': typeof AuthenticatedInventoryCategoriesRoute
'/members/$memberId': typeof AuthenticatedMembersMemberIdRoute
'/repair-batches/$batchId': typeof AuthenticatedRepairBatchesBatchIdRoute
'/repair-batches/new': typeof AuthenticatedRepairBatchesNewRoute
@@ -353,6 +387,7 @@ export interface FileRoutesByTo {
'/roles/new': typeof AuthenticatedRolesNewRoute
'/accounts': typeof AuthenticatedAccountsIndexRoute
'/files': typeof AuthenticatedFilesIndexRoute
'/inventory': typeof AuthenticatedInventoryIndexRoute
'/members': typeof AuthenticatedMembersIndexRoute
'/repair-batches': typeof AuthenticatedRepairBatchesIndexRoute
'/repairs': typeof AuthenticatedRepairsIndexRoute
@@ -370,6 +405,7 @@ export interface FileRoutesByTo {
'/lessons/templates/$templateId': typeof AuthenticatedLessonsTemplatesTemplateIdRoute
'/lessons/templates/new': typeof AuthenticatedLessonsTemplatesNewRoute
'/accounts/$accountId': typeof AuthenticatedAccountsAccountIdIndexRoute
'/inventory/suppliers': typeof AuthenticatedInventorySuppliersIndexRoute
'/lessons/enrollments': typeof AuthenticatedLessonsEnrollmentsIndexRoute
'/lessons/plans': typeof AuthenticatedLessonsPlansIndexRoute
'/lessons/schedule': typeof AuthenticatedLessonsScheduleIndexRoute
@@ -388,6 +424,8 @@ export interface FileRoutesById {
'/_authenticated/': typeof AuthenticatedIndexRoute
'/_authenticated/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren
'/_authenticated/accounts/new': typeof AuthenticatedAccountsNewRoute
'/_authenticated/inventory/$productId': typeof AuthenticatedInventoryProductIdRoute
'/_authenticated/inventory/categories': typeof AuthenticatedInventoryCategoriesRoute
'/_authenticated/members/$memberId': typeof AuthenticatedMembersMemberIdRoute
'/_authenticated/repair-batches/$batchId': typeof AuthenticatedRepairBatchesBatchIdRoute
'/_authenticated/repair-batches/new': typeof AuthenticatedRepairBatchesNewRoute
@@ -398,6 +436,7 @@ export interface FileRoutesById {
'/_authenticated/roles/new': typeof AuthenticatedRolesNewRoute
'/_authenticated/accounts/': typeof AuthenticatedAccountsIndexRoute
'/_authenticated/files/': typeof AuthenticatedFilesIndexRoute
'/_authenticated/inventory/': typeof AuthenticatedInventoryIndexRoute
'/_authenticated/members/': typeof AuthenticatedMembersIndexRoute
'/_authenticated/repair-batches/': typeof AuthenticatedRepairBatchesIndexRoute
'/_authenticated/repairs/': typeof AuthenticatedRepairsIndexRoute
@@ -415,6 +454,7 @@ export interface FileRoutesById {
'/_authenticated/lessons/templates/$templateId': typeof AuthenticatedLessonsTemplatesTemplateIdRoute
'/_authenticated/lessons/templates/new': typeof AuthenticatedLessonsTemplatesNewRoute
'/_authenticated/accounts/$accountId/': typeof AuthenticatedAccountsAccountIdIndexRoute
'/_authenticated/inventory/suppliers/': typeof AuthenticatedInventorySuppliersIndexRoute
'/_authenticated/lessons/enrollments/': typeof AuthenticatedLessonsEnrollmentsIndexRoute
'/_authenticated/lessons/plans/': typeof AuthenticatedLessonsPlansIndexRoute
'/_authenticated/lessons/schedule/': typeof AuthenticatedLessonsScheduleIndexRoute
@@ -433,6 +473,8 @@ export interface FileRouteTypes {
| '/users'
| '/accounts/$accountId'
| '/accounts/new'
| '/inventory/$productId'
| '/inventory/categories'
| '/members/$memberId'
| '/repair-batches/$batchId'
| '/repair-batches/new'
@@ -443,6 +485,7 @@ export interface FileRouteTypes {
| '/roles/new'
| '/accounts/'
| '/files/'
| '/inventory/'
| '/members/'
| '/repair-batches/'
| '/repairs/'
@@ -460,6 +503,7 @@ export interface FileRouteTypes {
| '/lessons/templates/$templateId'
| '/lessons/templates/new'
| '/accounts/$accountId/'
| '/inventory/suppliers/'
| '/lessons/enrollments/'
| '/lessons/plans/'
| '/lessons/schedule/'
@@ -475,6 +519,8 @@ export interface FileRouteTypes {
| '/users'
| '/'
| '/accounts/new'
| '/inventory/$productId'
| '/inventory/categories'
| '/members/$memberId'
| '/repair-batches/$batchId'
| '/repair-batches/new'
@@ -485,6 +531,7 @@ export interface FileRouteTypes {
| '/roles/new'
| '/accounts'
| '/files'
| '/inventory'
| '/members'
| '/repair-batches'
| '/repairs'
@@ -502,6 +549,7 @@ export interface FileRouteTypes {
| '/lessons/templates/$templateId'
| '/lessons/templates/new'
| '/accounts/$accountId'
| '/inventory/suppliers'
| '/lessons/enrollments'
| '/lessons/plans'
| '/lessons/schedule'
@@ -519,6 +567,8 @@ export interface FileRouteTypes {
| '/_authenticated/'
| '/_authenticated/accounts/$accountId'
| '/_authenticated/accounts/new'
| '/_authenticated/inventory/$productId'
| '/_authenticated/inventory/categories'
| '/_authenticated/members/$memberId'
| '/_authenticated/repair-batches/$batchId'
| '/_authenticated/repair-batches/new'
@@ -529,6 +579,7 @@ export interface FileRouteTypes {
| '/_authenticated/roles/new'
| '/_authenticated/accounts/'
| '/_authenticated/files/'
| '/_authenticated/inventory/'
| '/_authenticated/members/'
| '/_authenticated/repair-batches/'
| '/_authenticated/repairs/'
@@ -546,6 +597,7 @@ export interface FileRouteTypes {
| '/_authenticated/lessons/templates/$templateId'
| '/_authenticated/lessons/templates/new'
| '/_authenticated/accounts/$accountId/'
| '/_authenticated/inventory/suppliers/'
| '/_authenticated/lessons/enrollments/'
| '/_authenticated/lessons/plans/'
| '/_authenticated/lessons/schedule/'
@@ -645,6 +697,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticatedMembersIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/inventory/': {
id: '/_authenticated/inventory/'
path: '/inventory'
fullPath: '/inventory/'
preLoaderRoute: typeof AuthenticatedInventoryIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/files/': {
id: '/_authenticated/files/'
path: '/files'
@@ -715,6 +774,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticatedMembersMemberIdRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/inventory/categories': {
id: '/_authenticated/inventory/categories'
path: '/inventory/categories'
fullPath: '/inventory/categories'
preLoaderRoute: typeof AuthenticatedInventoryCategoriesRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/inventory/$productId': {
id: '/_authenticated/inventory/$productId'
path: '/inventory/$productId'
fullPath: '/inventory/$productId'
preLoaderRoute: typeof AuthenticatedInventoryProductIdRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/accounts/new': {
id: '/_authenticated/accounts/new'
path: '/accounts/new'
@@ -764,6 +837,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticatedLessonsEnrollmentsIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/inventory/suppliers/': {
id: '/_authenticated/inventory/suppliers/'
path: '/inventory/suppliers'
fullPath: '/inventory/suppliers/'
preLoaderRoute: typeof AuthenticatedInventorySuppliersIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/accounts/$accountId/': {
id: '/_authenticated/accounts/$accountId/'
path: '/'
@@ -896,6 +976,8 @@ interface AuthenticatedRouteChildren {
AuthenticatedIndexRoute: typeof AuthenticatedIndexRoute
AuthenticatedAccountsAccountIdRoute: typeof AuthenticatedAccountsAccountIdRouteWithChildren
AuthenticatedAccountsNewRoute: typeof AuthenticatedAccountsNewRoute
AuthenticatedInventoryProductIdRoute: typeof AuthenticatedInventoryProductIdRoute
AuthenticatedInventoryCategoriesRoute: typeof AuthenticatedInventoryCategoriesRoute
AuthenticatedMembersMemberIdRoute: typeof AuthenticatedMembersMemberIdRoute
AuthenticatedRepairBatchesBatchIdRoute: typeof AuthenticatedRepairBatchesBatchIdRoute
AuthenticatedRepairBatchesNewRoute: typeof AuthenticatedRepairBatchesNewRoute
@@ -906,6 +988,7 @@ interface AuthenticatedRouteChildren {
AuthenticatedRolesNewRoute: typeof AuthenticatedRolesNewRoute
AuthenticatedAccountsIndexRoute: typeof AuthenticatedAccountsIndexRoute
AuthenticatedFilesIndexRoute: typeof AuthenticatedFilesIndexRoute
AuthenticatedInventoryIndexRoute: typeof AuthenticatedInventoryIndexRoute
AuthenticatedMembersIndexRoute: typeof AuthenticatedMembersIndexRoute
AuthenticatedRepairBatchesIndexRoute: typeof AuthenticatedRepairBatchesIndexRoute
AuthenticatedRepairsIndexRoute: typeof AuthenticatedRepairsIndexRoute
@@ -917,6 +1000,7 @@ interface AuthenticatedRouteChildren {
AuthenticatedLessonsSessionsSessionIdRoute: typeof AuthenticatedLessonsSessionsSessionIdRoute
AuthenticatedLessonsTemplatesTemplateIdRoute: typeof AuthenticatedLessonsTemplatesTemplateIdRoute
AuthenticatedLessonsTemplatesNewRoute: typeof AuthenticatedLessonsTemplatesNewRoute
AuthenticatedInventorySuppliersIndexRoute: typeof AuthenticatedInventorySuppliersIndexRoute
AuthenticatedLessonsEnrollmentsIndexRoute: typeof AuthenticatedLessonsEnrollmentsIndexRoute
AuthenticatedLessonsPlansIndexRoute: typeof AuthenticatedLessonsPlansIndexRoute
AuthenticatedLessonsScheduleIndexRoute: typeof AuthenticatedLessonsScheduleIndexRoute
@@ -934,6 +1018,8 @@ const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
AuthenticatedAccountsAccountIdRoute:
AuthenticatedAccountsAccountIdRouteWithChildren,
AuthenticatedAccountsNewRoute: AuthenticatedAccountsNewRoute,
AuthenticatedInventoryProductIdRoute: AuthenticatedInventoryProductIdRoute,
AuthenticatedInventoryCategoriesRoute: AuthenticatedInventoryCategoriesRoute,
AuthenticatedMembersMemberIdRoute: AuthenticatedMembersMemberIdRoute,
AuthenticatedRepairBatchesBatchIdRoute:
AuthenticatedRepairBatchesBatchIdRoute,
@@ -945,6 +1031,7 @@ const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
AuthenticatedRolesNewRoute: AuthenticatedRolesNewRoute,
AuthenticatedAccountsIndexRoute: AuthenticatedAccountsIndexRoute,
AuthenticatedFilesIndexRoute: AuthenticatedFilesIndexRoute,
AuthenticatedInventoryIndexRoute: AuthenticatedInventoryIndexRoute,
AuthenticatedMembersIndexRoute: AuthenticatedMembersIndexRoute,
AuthenticatedRepairBatchesIndexRoute: AuthenticatedRepairBatchesIndexRoute,
AuthenticatedRepairsIndexRoute: AuthenticatedRepairsIndexRoute,
@@ -960,6 +1047,8 @@ const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
AuthenticatedLessonsTemplatesTemplateIdRoute:
AuthenticatedLessonsTemplatesTemplateIdRoute,
AuthenticatedLessonsTemplatesNewRoute: AuthenticatedLessonsTemplatesNewRoute,
AuthenticatedInventorySuppliersIndexRoute:
AuthenticatedInventorySuppliersIndexRoute,
AuthenticatedLessonsEnrollmentsIndexRoute:
AuthenticatedLessonsEnrollmentsIndexRoute,
AuthenticatedLessonsPlansIndexRoute: AuthenticatedLessonsPlansIndexRoute,