Files
QuantEngineByItz/oms-wms-erp/PHASE3-STEP2-SMART-COMPONENTS.md
T
kjh2064 b34b0dd7d6
Validators (Pushes and Pull Requests) / UI & Storage Validation (pull_request) Failing after 12s
Validators (Pushes and Pull Requests) / Database & Schema Validation (pull_request) Successful in 13s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (pull_request) Failing after 28s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (pull_request) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (pull_request) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (pull_request) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (pull_request) Has been skipped
Validators (Pushes and Pull Requests) / CI Workflow Lint (pull_request) Failing after 10s
Validators (Pushes and Pull Requests) / Security & Secrets (pull_request) Successful in 12s
Validators (Pushes and Pull Requests) / Notify PR Results (pull_request) Successful in 2s
Frontend CI Pipeline / ci-frontend-8-steps (pull_request) Failing after 2m50s
Add OMS WMS ERP platform
2026-07-27 00:45:39 +09:00

695 lines
16 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 3 Step 2: Smart Components Layer (Domain Fields)
**Status**: 🚀 START
**Phase**: 3 / 11
**Step**: 2 / 4
**Target Completion**: 2026-09-02 (4 days)
**Overall Phase 3**: 2 weeks (2026-08-27 to 2026-09-10)
---
## Overview
**Phase 3 Step 2 Goal**: Build Smart Components layer (Domain Fields)
**What are Domain Fields?**
- Composed from Typed Fields + business logic
- Add API lookups (customer list, product catalog, etc.)
- Implement domain-specific validation rules
- Enable complex workflows (multi-field coordination)
- Example: OrderLineField = Qty (NumberField) + Product (lookup) + Price (auto-calculated)
**Architecture Transition**:
```
Layer 1: Primitives (Button, Input, Table)
↓ (composed into)
Layer 2: Typed Fields (TextField, NumberField, DateField)
↓ (composed into)
Layer 3: Domain Fields ← WE ARE HERE (OrderLineField, CustomerField)
↓ (composed into)
Layer 4: Business Composites (OrderForm, InventoryTransfer)
```
**After Step 2**: Full Domain Fields layer ready for Composite components
---
## The 12 Domain Fields
### 1. **OrderLineField** — Order line item (qty + product + price)
```vue
<template>
<div class="order-line-group">
<!-- Product Lookup -->
<SelectField
v-model="line.productId"
:options="productOptions"
label="Product"
@update:modelValue="handleProductChange"
/>
<!-- Quantity (auto-calculates available) -->
<NumberField
v-model="line.quantity"
label="Quantity"
:max="availableQuantity"
@blur="calculateTotal"
/>
<!-- Unit Price (auto-filled from product) -->
<CurrencyField
v-model="line.unitPrice"
label="Unit Price"
disabled
/>
<!-- Total Line Amount (auto-calculated) -->
<CurrencyField
v-model="line.lineTotal"
label="Line Total"
disabled
/>
</div>
</template>
<script setup lang="ts">
interface OrderLine {
productId: string
quantity: number
unitPrice: number
lineTotal: number
}
const props = defineProps<{
modelValue: OrderLine
availableProducts?: any[]
}>()
const line = ref({...props.modelValue})
const productOptions = ref([])
const handleProductChange = async (productId: string) => {
// Fetch product details from API
const product = await productsApi.getProduct(productId)
line.value.unitPrice = product.price
calculateTotal()
}
const calculateTotal = () => {
line.value.lineTotal = line.value.quantity * line.value.unitPrice
emit('update:modelValue', line.value)
}
</script>
```
**Features**:
- Product lookup (async)
- Quantity validation (available stock)
- Auto-fill unit price from product
- Auto-calculate line total
- Emit coordinated updates
---
### 2. **CustomerField** — Customer lookup with details
```vue
<template>
<div class="customer-field">
<!-- Customer Autocomplete -->
<SelectField
v-model="customerId"
:options="customerOptions"
label="Customer"
searchable
async
@search="searchCustomers"
/>
<!-- Auto-filled Details -->
<TextField
:value="customer.name"
label="Company Name"
disabled
/>
<EmailField
:value="customer.email"
label="Email"
disabled
/>
<PhoneField
:value="customer.phone"
label="Contact Phone"
disabled
/>
<!-- Credit Limit Warning -->
<div v-if="customerAtRisk" class="alert alert-warning">
Customer approaching credit limit: {{ creditUsage }}%
</div>
</div>
</template>
<script setup lang="ts">
const customerId = ref('')
const customer = ref({})
const customerOptions = ref([])
const searchCustomers = async (query: string) => {
const results = await customersApi.searchCustomers(query)
customerOptions.value = results.map(c => ({ value: c.id, label: c.name }))
}
const handleCustomerSelect = async (id: string) => {
const customerData = await customersApi.getCustomer(id)
customer.value = customerData
checkCreditLimit(customerData)
emit('update:modelValue', { customerId: id, ...customerData })
}
</script>
```
**Features**:
- Async customer search
- Auto-load customer details
- Display contact info
- Credit limit warning
- Emit coordinated data
---
### 3. **ProductField** — Product lookup with details
```vue
<template>
<div class="product-field">
<!-- SKU Lookup -->
<TextField
v-model="sku"
label="Product SKU"
@blur="lookupProduct"
/>
<!-- Auto-filled Details -->
<TextField
:value="product.name"
label="Product Name"
disabled
/>
<SelectField
:value="product.categoryId"
:options="categories"
label="Category"
disabled
/>
<CurrencyField
:value="product.price"
label="List Price"
disabled
/>
<!-- Stock Availability -->
<NumberField
:value="availableStock"
label="Available Stock"
disabled
/>
</div>
</template>
<script setup lang="ts">
const sku = ref('')
const product = ref({})
const lookupProduct = async () => {
if (!sku.value) return
const p = await productsApi.getProductBySku(sku.value)
if (!p) {
errorMessage.value = 'Product not found'
return
}
product.value = p
checkStock(p.id)
emit('update:modelValue', p)
}
</script>
```
**Features**:
- SKU-based lookup
- Auto-load product details
- Display category, price, stock
- Validation (product exists)
---
### 4. **WarehouseField** — Warehouse selection with capacity
```vue
<template>
<div class="warehouse-field">
<SelectField
v-model="warehouseId"
:options="warehouseOptions"
label="Warehouse"
@update:modelValue="handleWarehouseChange"
/>
<!-- Display Warehouse Info -->
<div v-if="warehouse" class="warehouse-info">
<TextField :value="warehouse.location" label="Location" disabled />
<TextField :value="warehouse.capacity" label="Total Capacity" disabled />
<NumberField :value="warehouse.usedCapacity" label="Used Capacity" disabled />
<!-- Capacity Bar -->
<div class="progress mt-2">
<div
class="progress-bar"
:class="capacityClass"
:style="{ width: capacityPercent + '%' }"
>
{{ capacityPercent }}%
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const warehouseId = ref('')
const warehouse = ref(null)
const warehouseOptions = ref([])
onMounted(async () => {
const whs = await warehousesApi.listWarehouses()
warehouseOptions.value = whs.map(w => ({ value: w.id, label: w.name }))
})
const handleWarehouseChange = async (id: string) => {
warehouse.value = await warehousesApi.getWarehouse(id)
emit('update:modelValue', warehouse.value)
}
const capacityClass = computed(() => {
const percent = capacityPercent.value
if (percent > 90) return 'bg-danger'
if (percent > 70) return 'bg-warning'
return 'bg-success'
})
</script>
```
**Features**:
- Warehouse selection
- Display location, capacity
- Capacity usage visualization
- Color-coded status
---
### 5. **SupplierField** — Supplier lookup
```vue
<!-- Company name + Contact + Payment terms -->
```
### 6. **StockTransferField** — From/To warehouse transfer
```vue
<!-- From warehouse + To warehouse + Quantity + Transfer reason -->
```
### 7. **DateRangeField** — Start + End date pair
```vue
<!-- Start date + End date with validation (start < end) -->
```
### 8. **AddressField** — Full address with country
```vue
<!-- Street + City + Postal + Country + Validation -->
```
### 9. **BankAccountField** — Account + Bank code
```vue
<!-- Account number + Bank code + Account holder name -->
```
### 10. **TaxIDField** — Country-specific tax ID
```vue
<!-- Tax ID with country-specific validation (KRN, USN, JPN) -->
```
### 11. **RoleField** — User role with permissions
```vue
<!-- Role selection + Display role permissions + Permission matrix -->
```
### 12. **ApprovalField** — Approval with comments
```vue
<!-- Approver lookup + Approval status + Comment textarea + Timestamp -->
```
---
## Implementation Strategy
### Step 2.1: Core Domain Fields (2 days)
Implement 3 "must-have" fields:
1. **OrderLineField** — Most complex, demonstrates patterns
2. **CustomerField** — Async search pattern
3. **ProductField** — SKU lookup pattern
### Step 2.2: Supporting Domain Fields (1 day)
4. **WarehouseField** — Selection + info display
5. **DateRangeField** — Date pair validation
6. **AddressField** — Multi-field composite
### Step 2.3: Specialized Domain Fields (1 day)
7-12. Remaining 6 fields (simpler patterns)
---
## API Integration Pattern
**For each Domain Field**:
1. **Define API endpoint** (in MockServiceWorker)
```typescript
// tests/mocks/handlers.ts
http.get('*/api/products/search', async ({request}) => {
const url = new URL(request.url)
const query = url.searchParams.get('q')
return HttpResponse.json(searchResults)
})
```
2. **Create API client method**
```typescript
// src/services/api/client.ts
class ProductsApiClient extends ApiClient {
async searchProducts(query: string) {
return this.get('/products/search', { params: { q: query } })
}
}
```
3. **Use in Domain Field**
```typescript
// src/components/fields/domain/ProductField/ProductField.vue
import { productsApi } from '@/services/api/client'
const handleSkuChange = async (sku: string) => {
const product = await productsApi.getProductBySku(sku)
// ...
}
```
---
## Component Structure
**Each Domain Field** (3 files):
```
src/components/fields/domain/OrderLineField/
├── OrderLineField.vue (component implementation)
├── OrderLineField.stories.ts (5+ Storybook stories)
└── OrderLineField.spec.ts (10+ integration tests)
```
**Folder structure**:
```
src/components/fields/
├── primitives/ (30 components - Phase 1) ✅
├── typed/ (12 components - Phase 2+3.1) ✅
└── domain/ (12 components - Phase 3.2) ← START HERE
├── OrderLineField/
├── CustomerField/
├── ProductField/
├── WarehouseField/
├── SupplierField/
├── StockTransferField/
├── DateRangeField/
├── AddressField/
├── BankAccountField/
├── TaxIDField/
├── RoleField/
├── ApprovalField/
└── index.ts (central export)
```
---
## State Management Pattern
**For Domain Fields with complex state**:
Use Pinia store module or local state?
**Recommended**: Local state (with emit pattern) for Step 2
- Keep fields composable
- Avoid store bloat
- Parent form manages state via Pinia
**Example**:
```typescript
// OrderLineField (local state)
const line = ref({...modelValue})
watch(() => line.value, () => {
emit('update:modelValue', line.value)
}, { deep: true })
// Parent OrderForm (Pinia store)
const lineStore = useOrderLinesStore() // Pinia store
```
---
## Testing Strategy
### Unit Tests (Per Field: 15-20 tests)
```typescript
// OrderLineField.spec.ts example
describe('OrderLineField', () => {
it('loads product details on SKU change')
it('validates quantity against available stock')
it('calculates line total correctly')
it('emits update:modelValue on changes')
it('handles product not found error')
it('disables fields when loading')
it('shows loading spinner during API call')
})
```
### Integration Tests (New)
```typescript
// tests/integration/domain-fields.spec.ts
describe('Domain Fields with API', () => {
it('OrderLineField full workflow (search → select → calculate)')
it('CustomerField async search + credit check')
it('ProductField SKU lookup + stock validation')
})
```
### Storybook Stories (Per Field: 5+ stories)
```typescript
// OrderLineField.stories.ts
export const Default = {...}
export const WithProductLookup = {...}
export const WithValidationError = {...}
export const Loading = {...}
export const Disabled = {...}
```
---
## API Requirements
**New API endpoints needed** (for MSW mocking):
```
Products:
GET /api/products/search?q={query}
GET /api/products/{id}
GET /api/products/sku/{sku}
Customers:
GET /api/customers/search?q={query}
GET /api/customers/{id}
POST /api/customers/{id}/credit-check
Warehouses:
GET /api/warehouses
GET /api/warehouses/{id}
Suppliers:
GET /api/suppliers/search?q={query}
GET /api/suppliers/{id}
Orders:
GET /api/orders/{id}/lines
POST /api/orders/{id}/lines (line validation)
```
**Update MSW handlers** (tests/mocks/handlers.ts):
- [ ] Product search endpoint
- [ ] Product by SKU endpoint
- [ ] Customer search endpoint
- [ ] Customer credit check endpoint
- [ ] Warehouse list/get endpoints
- [ ] Supplier search endpoint
---
## Exit Criteria (Step 2 Complete)
- ✅ 12 Domain Fields implemented
- ✅ 60+ Storybook stories created
- ✅ 150+ integration tests passing
- ✅ API endpoints mocked (MSW)
- ✅ All async patterns tested
- ✅ Error handling verified
- ✅ Loading states implemented
- ✅ TypeScript strict: 0 errors
- ✅ WCAG accessibility compliance
---
## Timeline
| Task | Duration | Status |
|------|----------|--------|
| OrderLineField | 6 hours | ⏳ Start |
| CustomerField | 4 hours | ⏳ After OrderLineField |
| ProductField | 4 hours | ⏳ After CustomerField |
| WarehouseField | 3 hours | ⏳ Parallel |
| DateRangeField | 2 hours | ⏳ Parallel |
| AddressField | 3 hours | ⏳ Parallel |
| Remaining 6 fields | 6 hours | ⏳ Day 2 |
| Testing + Storybook | 4 hours | ⏳ Day 3 |
| **Total** | **~32 hours / 4 days** | ⏳ 2026-08-28 → 2026-09-02 |
---
## Quality Checklist (Per Field)
- [ ] Component renders correctly
- [ ] Props are typed (no `any`)
- [ ] Emits work (update:modelValue)
- [ ] API calls mocked (MSW)
- [ ] Loading state shows spinner
- [ ] Error state shows message
- [ ] Validation rules enforced
- [ ] Accessible (labels, ARIA, keyboard)
- [ ] 5+ Storybook stories
- [ ] 15+ integration tests
- [ ] TypeScript strict: 0 errors
- [ ] No console warnings
---
## Known Patterns to Implement
### 1. **Async Search**
```typescript
const searchQuery = ref('')
const searchResults = ref([])
const isSearching = ref(false)
const handleSearch = async (query: string) => {
isSearching.value = true
searchResults.value = await api.search(query)
isSearching.value = false
}
```
### 2. **Auto-fill Details**
```typescript
const handleSelect = async (id: string) => {
const details = await api.getDetails(id)
Object.assign(model, details)
emit('update:modelValue', model)
}
```
### 3. **Multi-field Validation**
```typescript
const validateLineTotal = () => {
if (line.quantity * line.unitPrice !== line.lineTotal) {
error.value = 'Line total mismatch'
}
}
```
### 4. **Loading State**
```typescript
const isLoading = ref(false)
const handleAsyncAction = async () => {
isLoading.value = true
try {
// async work
} finally {
isLoading.value = false
}
}
```
---
## Next Phase (After Step 2)
**Phase 3 Step 3**: Complete remaining Pinia stores (7/10 stores)
- Customers, Suppliers, StockTransfers, etc.
- Integration with Domain Fields
**Phase 3 Step 4**: Business Composite Components (11 components)
- OrderForm (uses OrderLineField, CustomerField, etc.)
- InventoryTransfer, VoucherEditor, etc.
---
## Files to Create
```
Phase 3 Step 2 Deliverables:
src/components/fields/domain/
├── index.ts (12 field exports)
├── OrderLineField/
│ ├── OrderLineField.vue
│ ├── OrderLineField.stories.ts
│ └── OrderLineField.spec.ts
├── CustomerField/
├── ProductField/
├── WarehouseField/
├── SupplierField/
├── StockTransferField/
├── DateRangeField/
├── AddressField/
├── BankAccountField/
├── TaxIDField/
├── RoleField/
└── ApprovalField/
tests/mocks/
└── handlers.ts (UPDATED: new API endpoints)
tests/integration/
└── domain-fields.spec.ts (NEW: 150+ tests)
Documentation/
└── PHASE3-STEP2-COMPLETION.md
```
---
**Ready to implement Phase 3 Step 2?**
Starting with **OrderLineField** (most complex, demonstrates patterns)
Estimated time: 4 days → 2026-09-02
**Proceed with OrderLineField implementation?** ✅ Yes