Update planning docs to reflect current implementation state
- Doc 02: Add member_identifier table, member_number, primary_member_id, account_number auto-generation, isMinor override, tax_exemption as separate table, member move, updated business rules - Doc 03: Document lookup table pattern replacing pgEnums for status and condition, add system/custom value distinction - Doc 22: Mark all Phase 2 items as complete, add new tables to additions section, update audit findings, note admin frontend exists
This commit is contained in:
@@ -68,7 +68,7 @@ account_number
|
|||||||
|
|
||||||
varchar
|
varchar
|
||||||
|
|
||||||
Human-readable account ID
|
Auto-generated 6-digit random number, unique per company. Human-readable account ID.
|
||||||
|
|
||||||
name
|
name
|
||||||
|
|
||||||
@@ -101,6 +101,12 @@ enum
|
|||||||
|
|
||||||
consolidated | split
|
consolidated | split
|
||||||
|
|
||||||
|
primary_member_id
|
||||||
|
|
||||||
|
uuid FK
|
||||||
|
|
||||||
|
Nullable — references the primary contact member on this account. Auto-set when first member is added.
|
||||||
|
|
||||||
is_active
|
is_active
|
||||||
|
|
||||||
boolean
|
boolean
|
||||||
@@ -201,6 +207,12 @@ uuid FK
|
|||||||
|
|
||||||
Tenant scoping — identifies which company owns this record
|
Tenant scoping — identifies which company owns this record
|
||||||
|
|
||||||
|
member_number
|
||||||
|
|
||||||
|
varchar
|
||||||
|
|
||||||
|
Auto-generated 6-digit random number, unique per company. Human-readable member ID.
|
||||||
|
|
||||||
first_name
|
first_name
|
||||||
|
|
||||||
varchar
|
varchar
|
||||||
@@ -217,13 +229,13 @@ date_of_birth
|
|||||||
|
|
||||||
date
|
date
|
||||||
|
|
||||||
Used to derive is_minor status
|
Optional — used to derive is_minor if set
|
||||||
|
|
||||||
is_minor
|
is_minor
|
||||||
|
|
||||||
boolean
|
boolean
|
||||||
|
|
||||||
True if under 18 — derived from date_of_birth, controls consent and portal access rules
|
Default false. Auto-derived from date_of_birth if provided, but can be set manually (e.g. parent declines to give DOB). Manual flag takes precedence over DOB calculation.
|
||||||
|
|
||||||
email
|
email
|
||||||
|
|
||||||
@@ -261,7 +273,30 @@ timestamptz
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 3.3 account_processor_link
|
## 3.3 member_identifier
|
||||||
|
|
||||||
|
Identity documents associated with a member. Supports multiple IDs per member (driver's license, passport, school ID). Images stored as base64 in Postgres for simplified backup/restore.
|
||||||
|
|
||||||
|
Column | Type | Notes
|
||||||
|
id | uuid PK |
|
||||||
|
member_id | uuid FK | References member
|
||||||
|
company_id | uuid FK | Tenant scoping
|
||||||
|
type | varchar | Constrained: drivers_license, passport, school_id
|
||||||
|
label | varchar | Optional display label
|
||||||
|
value | varchar | The ID number
|
||||||
|
issuing_authority | varchar | e.g. "State of Texas", "US Dept of State"
|
||||||
|
issued_date | date |
|
||||||
|
expires_at | date |
|
||||||
|
image_front | text | Base64-encoded front image
|
||||||
|
image_back | text | Base64-encoded back image
|
||||||
|
notes | text |
|
||||||
|
is_primary | boolean | Default false — which ID to display by default
|
||||||
|
created_at | timestamptz |
|
||||||
|
updated_at | timestamptz |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 3.4 account_processor_link
|
||||||
|
|
||||||
Links an account to a payment processor's customer record. This is processor-agnostic — supports Stripe, Global Payments, or any future processor. Replaces the previous `stripe_customer_id` column that was directly on the account table.
|
Links an account to a payment processor's customer record. This is processor-agnostic — supports Stripe, Global Payments, or any future processor. Replaces the previous `stripe_customer_id` column that was directly on the account table.
|
||||||
|
|
||||||
@@ -315,7 +350,7 @@ timestamptz
|
|||||||
|
|
||||||
An account may have links to multiple processors simultaneously — this is expected during migration from one processor to another (e.g. AIM legacy → Stripe transition).
|
An account may have links to multiple processors simultaneously — this is expected during migration from one processor to another (e.g. AIM legacy → Stripe transition).
|
||||||
|
|
||||||
## 3.4 account_payment_method
|
## 3.5 account_payment_method
|
||||||
|
|
||||||
Tracks payment methods on file. Card data lives in the processor — this table only stores references for display and selection.
|
Tracks payment methods on file. Card data lives in the processor — this table only stores references for display and selection.
|
||||||
|
|
||||||
@@ -399,6 +434,32 @@ timestamptz
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 3.6 tax_exemption
|
||||||
|
|
||||||
|
Tax exemptions are tracked in a separate table (not on account) for audit history. An account can have multiple exemption records over time (expired, revoked, renewed).
|
||||||
|
|
||||||
|
Column | Type | Notes
|
||||||
|
id | uuid PK |
|
||||||
|
account_id | uuid FK |
|
||||||
|
company_id | uuid FK | Tenant scoping
|
||||||
|
status | enum | none, pending, approved
|
||||||
|
certificate_number | varchar | Required
|
||||||
|
certificate_type | varchar | e.g. "resale", "nonprofit"
|
||||||
|
issuing_state | varchar(2) | State abbreviation
|
||||||
|
expires_at | date |
|
||||||
|
approved_by | uuid | Employee who verified
|
||||||
|
approved_at | timestamptz |
|
||||||
|
revoked_by | uuid |
|
||||||
|
revoked_at | timestamptz |
|
||||||
|
revoked_reason | text |
|
||||||
|
notes | text |
|
||||||
|
created_at | timestamptz |
|
||||||
|
updated_at | timestamptz |
|
||||||
|
|
||||||
|
Action endpoints: POST /tax-exemptions/:id/approve, POST /tax-exemptions/:id/revoke (requires reason).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 4. Business Rules
|
# 4. Business Rules
|
||||||
|
|
||||||
- Every member must belong to exactly one account
|
- Every member must belong to exactly one account
|
||||||
@@ -417,11 +478,23 @@ timestamptz
|
|||||||
|
|
||||||
- Duplicate account detection on email and phone during creation
|
- Duplicate account detection on email and phone during creation
|
||||||
|
|
||||||
|
- Account numbers auto-generated as 6-digit random numbers, unique per company
|
||||||
|
|
||||||
|
- Member numbers auto-generated as 6-digit random numbers, unique per company
|
||||||
|
|
||||||
|
- First member added to an account is auto-set as primary_member_id
|
||||||
|
|
||||||
|
- Members can be moved between accounts via POST /members/:id/move (optional accountId — creates new account if omitted)
|
||||||
|
|
||||||
|
- isMinor on member: explicit flag takes precedence, else derived from date_of_birth, else defaults to false
|
||||||
|
|
||||||
|
- Tax exemptions tracked in separate tax_exemption table for audit history — not on account directly
|
||||||
|
|
||||||
- Tax-exempt accounts must have a valid certificate number and expiry date before status can be set to "approved"
|
- Tax-exempt accounts must have a valid certificate number and expiry date before status can be set to "approved"
|
||||||
|
|
||||||
- Expired tax exemption certificates revert account to "pending" — staff prompted to collect updated certificate
|
- Expired tax exemption certificates revert to "pending" — staff prompted to collect updated certificate
|
||||||
|
|
||||||
- Tax exemption status changes logged in audit trail (who approved, when, certificate details)
|
- Tax exemption status changes logged with who approved/revoked, when, and reason
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -224,9 +224,26 @@ timestamptz
|
|||||||
|
|
||||||
Individual physical units for serialized items and rental fleet instruments.
|
Individual physical units for serialized items and rental fleet instruments.
|
||||||
|
|
||||||
id, product_id, company_id, location_id, serial_number,condition (new|excellent|good|fair|poor),status (available|sold|rented|on_trial|in_repair|layaway|lost|retired),purchase_date, purchase_cost, notes,legacy_id, created_at
|
id, product_id, company_id, location_id, serial_number, condition (varchar — references item_condition lookup), status (varchar — references inventory_unit_status lookup), purchase_date, purchase_cost, notes, legacy_id, created_at
|
||||||
|
|
||||||
### Status Values
|
**Note:** `condition` and `status` are stored as varchar columns referencing slug values in lookup tables (not pgEnums). This allows stores to add custom statuses and conditions. Code references system slugs for business logic; custom values are informational.
|
||||||
|
|
||||||
|
### Lookup Tables
|
||||||
|
|
||||||
|
Both `inventory_unit_status` and `item_condition` are company-scoped lookup tables with the pattern:
|
||||||
|
|
||||||
|
Column | Type | Notes
|
||||||
|
id | uuid PK |
|
||||||
|
company_id | uuid FK | Tenant scoping
|
||||||
|
name | varchar | Display name
|
||||||
|
slug | varchar | Code-level reference (unique per company)
|
||||||
|
description | text |
|
||||||
|
is_system | boolean | True = seeded by system, cannot be deleted or deactivated
|
||||||
|
sort_order | integer | Display ordering
|
||||||
|
is_active | boolean |
|
||||||
|
created_at | timestamptz |
|
||||||
|
|
||||||
|
### System Status Values (seeded per company)
|
||||||
|
|
||||||
Status | Description | Set By
|
Status | Description | Set By
|
||||||
available | In stock, ready for sale or rental | Default, return, restock
|
available | In stock, ready for sale or rental | Default, return, restock
|
||||||
@@ -238,6 +255,15 @@ layaway | Reserved for layaway customer, not available | Layaway creation (08_Do
|
|||||||
lost | Unrecovered — trial, rental, or inventory discrepancy | Overdue escalation or cycle count
|
lost | Unrecovered — trial, rental, or inventory discrepancy | Overdue escalation or cycle count
|
||||||
retired | Permanently removed from inventory | Manual retirement
|
retired | Permanently removed from inventory | Manual retirement
|
||||||
|
|
||||||
|
### System Condition Values (seeded per company)
|
||||||
|
|
||||||
|
Condition | Description
|
||||||
|
new | Brand new, unopened or unused
|
||||||
|
excellent | Like new, minimal signs of use
|
||||||
|
good | Normal wear, fully functional
|
||||||
|
fair | Noticeable wear, functional with minor issues
|
||||||
|
poor | Heavy wear, may need repair
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 4. Repair Parts Inventory
|
# 4. Repair Parts Inventory
|
||||||
|
|||||||
@@ -17,15 +17,19 @@ All deliverables built: monorepo config, Docker Compose (Postgres 16 + Valkey 8)
|
|||||||
Area | Status | Notes
|
Area | Status | Notes
|
||||||
Auth (JWT + bcrypt) | Complete | Self-issued JWTs, role enum, register/login routes
|
Auth (JWT + bcrypt) | Complete | Self-issued JWTs, role enum, register/login routes
|
||||||
User table | Complete | company_id FK, unique email, 5 roles
|
User table | Complete | company_id FK, unique email, 5 roles
|
||||||
Account table | Complete | Added is_active (not in spec — good defensive addition)
|
Account table | Complete | Added is_active, primary_member_id, auto-generated account_number
|
||||||
Member table | Complete | Added updated_at (not in spec — good practice)
|
Member table | Complete | Added updated_at, member_number, isMinor manual override
|
||||||
|
Member identifiers | Complete | DL/passport/school ID with front/back image storage
|
||||||
Account processor link | Complete | Processor-agnostic payment linking
|
Account processor link | Complete | Processor-agnostic payment linking
|
||||||
Account payment method | NOT BUILT | Specified in doc 02 but not implemented
|
Account payment method | Complete | Card references with default flag, requires_update for migrations
|
||||||
|
Tax exemptions | Complete | Separate table with approve/revoke workflow and audit trail
|
||||||
Category table | Complete | Added parent_id for hierarchy, sort_order, is_active
|
Category table | Complete | Added parent_id for hierarchy, sort_order, is_active
|
||||||
Supplier table | Complete | All columns present
|
Supplier table | Complete | All columns present
|
||||||
Product table | Complete | Cost removed — moved to stock_receipt (FIFO)
|
Product table | Complete | Cost removed — moved to stock_receipt (FIFO)
|
||||||
Inventory unit table | Complete | Condition enum, status enum, serial number
|
Inventory unit table | Complete | Condition and status via lookup tables (not enums), serial number
|
||||||
Shared Zod schemas | Complete | Auth, account, member, inventory schemas
|
Lookup tables | Complete | inventory_unit_status and item_condition as company-scoped configurable tables
|
||||||
|
Shared Zod schemas | Complete | Auth, account, member, identifier, payment method, tax exemption, lookup, inventory schemas
|
||||||
|
Admin frontend | Complete | React + Vite + shadcn/ui, accounts CRUD, members list, theme system
|
||||||
|
|
||||||
## 1.3 Additions Not in Original Planning
|
## 1.3 Additions Not in Original Planning
|
||||||
|
|
||||||
@@ -34,6 +38,11 @@ stock_receipt | FIFO cost tracking per purchase event | Replaces product.cost co
|
|||||||
price_history | Logs every retail price change | Auto-logged on product update
|
price_history | Logs every retail price change | Auto-logged on product update
|
||||||
consignment_detail | Consignment product linking | Links product to consignor account with commission %
|
consignment_detail | Consignment product linking | Links product to consignor account with commission %
|
||||||
product_supplier | Many-to-many product ↔ supplier | Tracks supplier SKU and preferred supplier
|
product_supplier | Many-to-many product ↔ supplier | Tracks supplier SKU and preferred supplier
|
||||||
|
inventory_unit_status | Lookup table replacing pgEnum | Company-scoped, is_system flag, custom values allowed
|
||||||
|
item_condition | Lookup table replacing pgEnum | Same pattern as unit status
|
||||||
|
member_identifier | Identity documents per member | DL, passport, school ID with base64 image storage
|
||||||
|
tax_exemption | Tax exempt certificates per account | Approve/revoke workflow with audit trail
|
||||||
|
account_payment_method | Card references per account | Processor-agnostic, default flag, migration support
|
||||||
|
|
||||||
## 1.4 Planning Doc Inconsistencies
|
## 1.4 Planning Doc Inconsistencies
|
||||||
|
|
||||||
@@ -56,8 +65,8 @@ Dev auth had no production guard | HIGH | Dev auth plugin throws error if NODE_E
|
|||||||
## 2.2 Issues Found — Not Yet Fixed (Lower Priority)
|
## 2.2 Issues Found — Not Yet Fixed (Lower Priority)
|
||||||
|
|
||||||
Issue | Severity | Notes
|
Issue | Severity | Notes
|
||||||
CORS in production is false | Medium | Blocks all cross-origin requests. Needs origin whitelist via CORS_ORIGINS env var. Fix when building frontend.
|
CORS in production is false | Medium | Blocks all cross-origin requests. Needs origin whitelist via CORS_ORIGINS env var. Admin frontend uses Vite proxy in dev — CORS not needed yet.
|
||||||
No pagination on list endpoints | Medium | list() returns .limit(100) with no offset. Add cursor/offset pagination when data volumes grow.
|
~~No pagination on list endpoints~~ | ~~Medium~~ | FIXED — All list endpoints now support server-side pagination, search, and sort.
|
||||||
Path parameters not validated as UUIDs | Low | Cast with `as { id: string }` but not validated. Drizzle handles gracefully (returns null). Add Zod param validation later.
|
Path parameters not validated as UUIDs | Low | Cast with `as { id: string }` but not validated. Drizzle handles gracefully (returns null). Add Zod param validation later.
|
||||||
Login schema accepts min(1) password | Low | Should match register's min(8) for consistency. Not a real security risk since bcrypt compare handles it.
|
Login schema accepts min(1) password | Low | Should match register's min(8) for consistency. Not a real security risk since bcrypt compare handles it.
|
||||||
No JWT secret strength validation | Low | Only checks if set, not length/entropy. Add min 32 char check.
|
No JWT secret strength validation | Low | Only checks if set, not length/entropy. Add min 32 char check.
|
||||||
|
|||||||
Reference in New Issue
Block a user