Add auto-generated account numbers and member numbers
6-digit random numbers generated on create, unique per company. Member number column added to member table. Both displayed in UI tables.
This commit is contained in:
@@ -7,7 +7,7 @@ import { DataTable, type Column } from '@/components/shared/data-table'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Search } from 'lucide-react'
|
||||
import { Search, Plus } from 'lucide-react'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/members')({
|
||||
validateSearch: (search: Record<string, unknown>) => ({
|
||||
@@ -21,6 +21,11 @@ export const Route = createFileRoute('/_authenticated/members')({
|
||||
})
|
||||
|
||||
const memberColumns: Column<MemberWithAccount>[] = [
|
||||
{
|
||||
key: 'memberNumber',
|
||||
header: '#',
|
||||
render: (row) => <span className="font-mono text-sm text-muted-foreground">{row.memberNumber ?? '-'}</span>,
|
||||
},
|
||||
{
|
||||
key: 'last_name',
|
||||
header: 'Name',
|
||||
@@ -68,7 +73,13 @@ function MembersListPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-2xl font-bold">Members</h1>
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Members</h1>
|
||||
<Button onClick={() => navigate({ to: '/accounts/new' })}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
New Member
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSearchSubmit} className="flex gap-2 max-w-sm">
|
||||
<div className="relative flex-1">
|
||||
|
||||
@@ -3,6 +3,7 @@ export interface Account {
|
||||
companyId: string
|
||||
accountNumber: string | null
|
||||
name: string
|
||||
primaryMemberId: string | null
|
||||
email: string | null
|
||||
phone: string | null
|
||||
address: {
|
||||
@@ -25,6 +26,7 @@ export interface Member {
|
||||
id: string
|
||||
accountId: string
|
||||
companyId: string
|
||||
memberNumber: string | null
|
||||
firstName: string
|
||||
lastName: string
|
||||
dateOfBirth: string | null
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add member_number to member table
|
||||
ALTER TABLE "member" ADD COLUMN "member_number" varchar(50);
|
||||
@@ -64,6 +64,13 @@
|
||||
"when": 1774702800000,
|
||||
"tag": "0008_member_primary_account",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "7",
|
||||
"when": 1774703400000,
|
||||
"tag": "0009_member_number",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -49,6 +49,7 @@ export const members = pgTable('member', {
|
||||
companyId: uuid('company_id')
|
||||
.notNull()
|
||||
.references(() => companies.id),
|
||||
memberNumber: varchar('member_number', { length: 50 }),
|
||||
firstName: varchar('first_name', { length: 100 }).notNull(),
|
||||
lastName: varchar('last_name', { length: 100 }).notNull(),
|
||||
dateOfBirth: date('date_of_birth'),
|
||||
|
||||
@@ -26,12 +26,35 @@ import {
|
||||
paginatedResponse,
|
||||
} from '../utils/pagination.js'
|
||||
|
||||
async function generateUniqueNumber(
|
||||
db: PostgresJsDatabase,
|
||||
table: typeof accounts | typeof members,
|
||||
column: typeof accounts.accountNumber | typeof members.memberNumber,
|
||||
companyId: string,
|
||||
companyIdColumn: typeof accounts.companyId,
|
||||
): Promise<string> {
|
||||
for (let attempt = 0; attempt < 10; attempt++) {
|
||||
const num = String(Math.floor(100000 + Math.random() * 900000))
|
||||
const [existing] = await db
|
||||
.select({ id: table.id })
|
||||
.from(table)
|
||||
.where(and(eq(companyIdColumn, companyId), eq(column, num)))
|
||||
.limit(1)
|
||||
if (!existing) return num
|
||||
}
|
||||
// Fallback to 8 digits if 6-digit space is crowded
|
||||
return String(Math.floor(10000000 + Math.random() * 90000000))
|
||||
}
|
||||
|
||||
export const AccountService = {
|
||||
async create(db: PostgresJsDatabase, companyId: string, input: AccountCreateInput) {
|
||||
const accountNumber = await generateUniqueNumber(db, accounts, accounts.accountNumber, companyId, accounts.companyId)
|
||||
|
||||
const [account] = await db
|
||||
.insert(accounts)
|
||||
.values({
|
||||
companyId,
|
||||
accountNumber,
|
||||
name: input.name,
|
||||
email: input.email,
|
||||
phone: input.phone,
|
||||
@@ -137,11 +160,13 @@ export const MemberService = {
|
||||
) {
|
||||
// isMinor: explicit flag wins, else derive from DOB, else false
|
||||
const minor = input.isMinor ?? (input.dateOfBirth ? isMinor(input.dateOfBirth) : false)
|
||||
const memberNumber = await generateUniqueNumber(db, members, members.memberNumber, companyId, members.companyId)
|
||||
|
||||
const [member] = await db
|
||||
.insert(members)
|
||||
.values({
|
||||
companyId,
|
||||
memberNumber,
|
||||
accountId: input.accountId,
|
||||
firstName: input.firstName,
|
||||
lastName: input.lastName,
|
||||
|
||||
Reference in New Issue
Block a user