Files
QuantEngineByItz/oms-wms-erp/DEVELOPMENT.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

11 KiB

Development Guide

Quick Start

1. First-Time Setup

# Clone repository
git clone <repo-url>
cd oms-wms-erp

# Install dependencies (once)
npm install

# Or use Makefile
make install

2. Start Development

Option A: Using npm

# Terminal 1: Start Vite dev server
npm run dev
# → http://localhost:5173

# Terminal 2: Start Storybook
npm run storybook
# → http://localhost:6006

Option B: Using Makefile

# Terminal 1
make dev

# Terminal 2
make storybook

3. Quality Checks

# Before committing, run:
make verify

# Or individual checks:
npm run lint           # ESLint + Prettier
npm run type-check     # TypeScript
npm run test:unit      # Unit tests
npm run build          # Production build

Project Structure

src/
├── components/
│   ├── primitives/        # Layer 1: UI building blocks
│   │   ├── Button/
│   │   ├── Input/
│   │   ├── Select/
│   │   └── ... (30 total)
│   ├── fields/
│   │   ├── typed/         # Layer 2: Type-safe inputs
│   │   │   ├── TextField/
│   │   │   └── ... (12 total)
│   │   └── domain/        # Layer 3: Business-specific
│   │       ├── OrderLineField/
│   │       └── ... (12 total)
│   └── composites/        # Layer 4: Full workflows
│       ├── Order/
│       └── ... (11 total)
├── stores/                # Pinia state management
│   └── modules/
│       ├── orders.ts
│       ├── inventory.ts
│       └── ... (10 total)
├── views/                 # Page components
│   ├── Home.vue
│   ├── Order/OrderList.vue
│   ├── Order/OrderForm.vue
│   └── ...
├── services/              # API client, validators, formatters
│   ├── api/
│   ├── validators/
│   └── formatters/
├── router.ts              # Vue Router configuration
├── App.vue                # Root component
└── main.ts                # Entry point

Creating New Components

1. Primitive Component (Layer 1)

Example: Create TextareaBase

# Create folder
mkdir -p src/components/primitives/Textarea

# Create component files
cat > src/components/primitives/Textarea/TextareaBase.vue << 'EOF'
<template>
  <div class="mb-3">
    <label v-if="label" :for="id" class="form-label">
      {{ label }}
      <span v-if="required" class="text-danger">*</span>
    </label>
    <textarea
      :id="id"
      :value="modelValue"
      :placeholder="placeholder"
      :rows="rows"
      :disabled="disabled"
      :class="['form-control', { 'is-invalid': errorMessage }]"
      @input="$emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)"
      @blur="$emit('blur')"
    />
    <div v-if="errorMessage" :id="`error-${id}`" class="invalid-feedback d-block">
      {{ errorMessage }}
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

interface Props {
  modelValue: string
  label?: string
  placeholder?: string
  rows?: number
  disabled?: boolean
  required?: boolean
  errorMessage?: string
}

withDefaults(defineProps<Props>(), {
  rows: 4
})

const id = ref(`textarea-${Math.random().toString(36).slice(2, 11)}`)

defineEmits<{
  'update:modelValue': [value: string]
  blur: []
}>()
</script>
EOF

# Create Storybook stories
cat > src/components/primitives/Textarea/TextareaBase.stories.ts << 'EOF'
import type { Meta, StoryObj } from '@storybook/vue3'
import TextareaBase from './TextareaBase.vue'

const meta = {
  title: 'Primitives/Textarea',
  component: TextareaBase,
  tags: ['autodocs']
} satisfies Meta<typeof TextareaBase>

export default meta
type Story = StoryObj<typeof meta>

export const Default: Story = {
  args: {
    label: 'Comments',
    placeholder: 'Enter your comments here...',
    rows: 4
  }
}

export const Error: Story = {
  args: {
    label: 'Comments',
    errorMessage: 'This field is required'
  }
}
EOF

# Create unit tests
cat > src/components/primitives/Textarea/TextareaBase.spec.ts << 'EOF'
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import TextareaBase from './TextareaBase.vue'

describe('TextareaBase', () => {
  it('renders with label', () => {
    const wrapper = mount(TextareaBase, {
      props: {
        label: 'Comments'
      }
    })
    expect(wrapper.text()).toContain('Comments')
  })

  it('emits update:modelValue on input', async () => {
    const wrapper = mount(TextareaBase, {
      props: {
        modelValue: ''
      }
    })
    await wrapper.find('textarea').setValue('test text')
    expect(wrapper.emitted('update:modelValue')).toBeDefined()
  })
})
EOF

2. Typed Field Component (Layer 2)

Example: Create TimeField

mkdir -p src/components/fields/typed/TimeField

# Create TimeField.vue (similar to DateField but for time input)
# Create TimeField.stories.ts (time picker stories)
# Create TimeField.spec.ts (validation tests)

3. Domain Field Component (Layer 3)

Example: Create PriceField

mkdir -p src/components/fields/domain/PriceField

# Create PriceField.vue (extends CurrencyField with KIS tick rules)
# Create PriceField.stories.ts (price input with suggestions)
# Create PriceField.spec.ts (tick rule validation)

4. Business Composite Component (Layer 4)

Example: Create CustomerForm

mkdir -p src/components/composites/Customer

# Create CustomerForm.vue (full CRUD form)
# Create CustomerForm.stories.ts (create/edit modes)
# Create CustomerForm.spec.ts (form submission, validation)

Component Best Practices

Props & Events

// Good: Strongly typed
interface Props {
  modelValue: string
  label?: string
  disabled?: boolean
  errorMessage?: string
}

// ❌ Bad: Loose typing
props: {
  value: String,  // No type definition
  options: Array  // Unclear structure
}

Slots

<template>
  <!-- Named slots for flexibility -->
  <div class="card">
    <div class="card-header">
      <slot name="header">Default Header</slot>
    </div>
    <div class="card-body">
      <slot />
    </div>
    <div class="card-footer">
      <slot name="footer" />
    </div>
  </div>
</template>

Accessibility

<template>
  <!-- Always use labels -->
  <label :for="id" class="form-label">Name</label>
  <input :id="id" :aria-describedby="errorId" />
  
  <!-- Describe error messages -->
  <div :id="errorId" v-if="error" class="invalid-feedback">
    {{ error }}
  </div>
</template>

Testing

import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'

describe('MyComponent', () => {
  it('renders with props', () => {
    const wrapper = mount(MyComponent, {
      props: { label: 'Test' }
    })
    expect(wrapper.find('label').text()).toBe('Test')
  })

  it('emits events', async () => {
    const wrapper = mount(MyComponent)
    await wrapper.find('button').trigger('click')
    expect(wrapper.emitted('click')).toHaveLength(1)
  })
})

Storybook Stories

Basic Story

import type { Meta, StoryObj } from '@storybook/vue3'
import MyComponent from './MyComponent.vue'

const meta = {
  title: 'Primitives/MyComponent',
  component: MyComponent,
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'danger']
    },
    size: {
      control: 'select',
      options: ['sm', 'md', 'lg']
    }
  }
} satisfies Meta<typeof MyComponent>

export default meta
type Story = StoryObj<typeof meta>

export const Primary: Story = {
  args: {
    variant: 'primary',
    size: 'md'
  },
  slots: {
    default: 'Click me'
  }
}

export const Secondary: Story = {
  args: {
    variant: 'secondary'
  }
}

Story with Controls

export const Interactive: Story = {
  args: {
    label: 'Username',
    placeholder: 'Enter username',
    disabled: false,
    required: true
  },
  argTypes: {
    disabled: { control: 'boolean' },
    required: { control: 'boolean' }
  }
}

Running Tests

Unit Tests

# Run all tests
npm run test:unit

# Run specific test
npm run test:unit -- ButtonBase.spec.ts

# Watch mode (re-run on file changes)
npm run test:unit -- --watch

# Coverage report
npm run test:unit -- --coverage

Integration Tests

# Run with Vitest + MSW mocks
npm run test:integration

E2E Tests

# Run Playwright tests
npm run test:e2e

# Run specific test
npm run test:e2e -- order-crud.spec.ts

# Debug mode
npm run test:e2e -- --debug

# UI mode (interactive)
npm run test:e2e -- --ui

Code Quality

ESLint

# Check all files
npm run lint

# Fix automatically
npm run lint -- --fix

# Check specific file
npx eslint src/components/Button/ButtonBase.vue

TypeScript

# Run type checker
npm run type-check

# Show errors
npx vue-tsc --noEmit --pretty

Prettier

# Format all files
npm run format

# Check formatting
npm run format:check

Build & Deployment

Development Build

npm run build:dev
# Creates dist/ with source maps

Production Build

npm run build
# Creates optimized dist/ <500KB (gzipped)

Preview Production Build

npm run preview
# Serves dist/ on http://localhost:4173/

Build Storybook

npm run build-storybook
# Creates storybook-static/
# Deploy to GitHub Pages or Chromatic

Troubleshooting

Port Already in Use

# Use different port
npm run dev -- --port 5174
npm run storybook -- -p 6007

Clear Cache

# Remove Vite cache
rm -rf node_modules/.vite

# Remove node_modules completely
rm -rf node_modules/
npm install

TypeScript Errors

# See detailed errors
npm run type-check

# Fix common issues
# 1. Remove unused imports
# 2. Add type definitions
# 3. Fix implicit `any` types

Test Timeouts

# Increase timeout
npm run test:unit -- --timeout=20000

Git Workflow

Before Committing

# Run all checks
make verify

# This runs:
# 1. ESLint (auto-fix)
# 2. TypeScript check
# 3. Unit tests
# 4. Production build

Commit Message Format

feat(component): Add new Button component

- Implement primary, secondary, danger variants
- Add loading and disabled states
- Add 7 Storybook stories
- Add 8 unit tests (100% coverage)
- Add WCAG 2.1 AA accessibility

Closes #123

Pre-commit Hooks

Husky automatically runs lint-staged before each commit:

  • ESLint auto-fixes staged files
  • Prettier formats staged files
  • Commit blocked if errors found

Phase 1 Checklist

  • Vite + Vue 3 + TypeScript configured
  • Storybook 7.0 with 8 addons
  • ESLint + Prettier setup
  • Pre-commit hooks (husky + lint-staged)
  • 5 initial Primitive components
  • 8 unit tests passing
  • 7 Storybook stories
  • npm run dev works (http://localhost:5173)
  • npm run storybook works (http://localhost:6006)
  • npm run build succeeds (<500KB)
  • CI/CD pipeline configured (GitHub Actions)
  • All developers can build locally

Next Steps

  1. Phase 2: Create remaining 25 Primitive components (Weeks 3-4)
  2. Phase 3: Create Typed & Domain Fields + Pinia (Weeks 5-6)
  3. Phase 4: Create Business Composites + E2E tests (Weeks 7-8)

Questions? See README.md or PHASE1-STEP2-VERIFICATION.md