Add OMS WMS ERP platform
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

This commit is contained in:
2026-07-27 00:45:39 +09:00
parent 15dc3685df
commit b34b0dd7d6
163 changed files with 32596 additions and 0 deletions
@@ -0,0 +1,395 @@
<template>
<div class="tax-id-field">
<div class="tax-id-group">
<!-- Country Selection -->
<div class="form-group">
<label class="form-label">
Country
<span class="text-danger">*</span>
</label>
<select
v-model="taxId.country"
class="form-control"
:class="{ 'is-invalid': countryError }"
@blur="validateCountry"
>
<option value="">-- Select Country --</option>
<option value="KR">South Korea (사업자등록번호)</option>
<option value="US">United States (EIN)</option>
<option value="JP">Japan (法人番号)</option>
<option value="CN">China (统一社会信用代码)</option>
<option value="SG">Singapore (UEN)</option>
<option value="TW">Taiwan (統一編號)</option>
</select>
<div v-if="countryError" class="invalid-feedback d-block">
{{ countryError }}
</div>
</div>
<!-- Tax ID Number -->
<div class="form-group col-full">
<label class="form-label">
{{ getFieldLabel() }}
<span class="text-danger">*</span>
</label>
<input
v-model="taxId.taxIdNumber"
type="text"
class="form-control"
:class="{ 'is-invalid': taxIdError }"
:placeholder="getPlaceholder()"
@blur="validateTaxId"
/>
<small class="text-muted d-block mt-1">
{{ getFormatHint() }}
</small>
<div v-if="taxIdError" class="invalid-feedback d-block">
{{ taxIdError }}
</div>
</div>
<!-- Company/Individual Type (optional) -->
<div class="form-group">
<label class="form-label">Type</label>
<select v-model="taxId.entityType" class="form-control">
<option value="">-- Select Type --</option>
<option value="INDIVIDUAL">Individual</option>
<option value="BUSINESS">Business</option>
<option value="CORPORATION">Corporation</option>
<option value="PARTNERSHIP">Partnership</option>
</select>
</div>
<!-- Verification Status (optional) -->
<div class="form-group">
<label class="form-label">Verification Status</label>
<select v-model="taxId.verificationStatus" class="form-control">
<option value="">-- Not Verified --</option>
<option value="PENDING">Pending</option>
<option value="VERIFIED">Verified</option>
<option value="FAILED">Failed</option>
</select>
</div>
</div>
<!-- Tax ID Summary -->
<div v-if="isComplete" class="tax-id-summary mt-2">
<small class="text-muted">
📋 <strong>{{ getCountryName(taxId.country) }}</strong>
{{ formatTaxId() }}
<span v-if="taxId.verificationStatus" class="badge" :class="getStatusBadgeClass()">
{{ taxId.verificationStatus }}
</span>
</small>
</div>
<!-- Validation Status -->
<div v-if="hasErrors" class="alert alert-danger mt-2">
Please complete all required fields
</div>
<div v-if="isComplete && !hasErrors" class="alert alert-success mt-2">
Tax ID information is valid
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
interface TaxID {
country: string
taxIdNumber: string
entityType?: string
verificationStatus?: string
}
const props = defineProps<{
modelValue: TaxID | null
}>()
const emit = defineEmits<{
'update:modelValue': [value: TaxID | null]
}>()
// State
const taxId = ref<TaxID>({
country: props.modelValue?.country || '',
taxIdNumber: props.modelValue?.taxIdNumber || '',
entityType: props.modelValue?.entityType || '',
verificationStatus: props.modelValue?.verificationStatus || ''
})
const countryError = ref<string | null>(null)
const taxIdError = ref<string | null>(null)
// Computed
const isComplete = computed(() => {
return (
taxId.value.country.length > 0 &&
taxId.value.taxIdNumber.trim().length > 0 &&
!hasErrors.value
)
})
const hasErrors = computed(() => {
return countryError.value !== null || taxIdError.value !== null
})
// Methods
const validateCountry = () => {
countryError.value = null
if (!taxId.value.country) {
countryError.value = 'Please select a country'
return
}
emitUpdate()
}
const validateTaxId = () => {
taxIdError.value = null
if (!taxId.value.taxIdNumber.trim()) {
taxIdError.value = 'Tax ID is required'
return
}
// Validate based on country
const country = taxId.value.country
const value = taxId.value.taxIdNumber.trim()
if (country === 'KR') {
// Korean: 사업자등록번호 (10 digits with optional hyphens: XXX-XX-XXXXX)
if (!/^(\d{3}-\d{2}-\d{5}|\d{10})$/.test(value)) {
taxIdError.value = 'Korean Tax ID format: XXX-XX-XXXXX or 10 digits'
return
}
} else if (country === 'US') {
// US: EIN (9 digits with optional hyphen: XX-XXXXXXX)
if (!/^(\d{2}-\d{7}|\d{9})$/.test(value)) {
taxIdError.value = 'US EIN format: XX-XXXXXXX or 9 digits'
return
}
} else if (country === 'JP') {
// Japan: 12 digits (XXXXXXXXXXXX)
if (!/^\d{12,13}$/.test(value)) {
taxIdError.value = 'Japan Tax ID format: 12-13 digits'
return
}
} else if (country === 'CN') {
// China: 18 digits (统一社会信用代码)
if (!/^\d{18}$/.test(value)) {
taxIdError.value = 'China Tax ID format: 18 digits'
return
}
} else if (country === 'SG') {
// Singapore: UEN (9 digits with optional hyphen: XXXXXXXXX or XXX-XXXXXX)
if (!/^(\d{9}|\d{3}-\d{6})$/.test(value)) {
taxIdError.value = 'Singapore UEN format: 9 digits or XXX-XXXXXX'
return
}
} else if (country === 'TW') {
// Taiwan: 8 digits (統一編號)
if (!/^\d{8}$/.test(value)) {
taxIdError.value = 'Taiwan Tax ID format: 8 digits'
return
}
}
emitUpdate()
}
const emitUpdate = () => {
if (isComplete.value) {
emit('update:modelValue', { ...taxId.value })
}
}
const getFieldLabel = (): string => {
const labels: Record<string, string> = {
KR: 'Business Registration Number (사업자등록번호)',
US: 'Employer Identification Number (EIN)',
JP: 'Corporate Number (法人番号)',
CN: 'Unified Social Credit Code (统一社会信用代码)',
SG: 'Unique Entity Number (UEN)',
TW: 'Uniform Number (統一編號)'
}
return labels[taxId.value.country] || 'Tax ID Number'
}
const getPlaceholder = (): string => {
const placeholders: Record<string, string> = {
KR: 'e.g., 123-45-67890',
US: 'e.g., 12-3456789',
JP: 'e.g., 1234567890123',
CN: 'e.g., 123456789012345678',
SG: 'e.g., 123456789 or 123-456789',
TW: 'e.g., 12345678'
}
return placeholders[taxId.value.country] || ''
}
const getFormatHint = (): string => {
const hints: Record<string, string> = {
KR: 'Format: XXX-XX-XXXXX (10 digits)',
US: 'Format: XX-XXXXXXX (9 digits)',
JP: 'Format: 12-13 digits',
CN: 'Format: 18 digits',
SG: 'Format: 9 digits or XXX-XXXXXX',
TW: 'Format: 8 digits'
}
return hints[taxId.value.country] || 'Format: country-specific'
}
const getCountryName = (code: string): string => {
const countries: Record<string, string> = {
KR: 'South Korea',
US: 'United States',
JP: 'Japan',
CN: 'China',
SG: 'Singapore',
TW: 'Taiwan'
}
return countries[code] || 'Country'
}
const formatTaxId = (): string => {
const value = taxId.value.taxIdNumber
const country = taxId.value.country
// Format with country indicator
return `${getCountryName(country)}: ${value}`
}
const getStatusBadgeClass = (): string => {
const status = taxId.value.verificationStatus
if (status === 'VERIFIED') return 'bg-success'
if (status === 'PENDING') return 'bg-warning'
if (status === 'FAILED') return 'bg-danger'
return 'bg-secondary'
}
</script>
<style scoped>
.tax-id-field {
margin-bottom: 1.5rem;
}
.tax-id-group {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
padding: 1rem;
border: 1px solid #dee2e6;
border-radius: 4px;
background-color: #f8f9fa;
}
.col-full {
grid-column: 1 / -1;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-label {
font-weight: 500;
margin-bottom: 0.5rem;
font-size: 0.875rem;
}
.form-control {
border-radius: 4px;
border: 1px solid #dee2e6;
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
font-family: inherit;
}
.form-control:focus {
border-color: #80bdff;
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.form-control.is-invalid {
border-color: #dc3545;
}
.text-danger {
color: #dc3545;
margin-left: 0.25rem;
}
.text-muted {
color: #6c757d;
font-size: 0.875rem;
}
.invalid-feedback {
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
display: block;
}
.d-block {
display: block;
}
.mt-1 {
margin-top: 0.25rem;
}
.mt-2 {
margin-top: 0.5rem;
}
.tax-id-summary {
padding: 0.75rem;
background-color: #e7f3ff;
border-left: 3px solid #0d6efd;
border-radius: 4px;
font-size: 0.875rem;
line-height: 1.6;
}
.badge {
font-size: 0.7rem;
padding: 0.25rem 0.5rem;
margin-left: 0.5rem;
vertical-align: middle;
}
.alert {
padding: 0.75rem 1rem;
border-radius: 4px;
font-size: 0.875rem;
border: 1px solid transparent;
margin-bottom: 1rem;
}
.alert-danger {
background-color: #f8d7da;
color: #721c24;
border-color: #f5c6cb;
}
.alert-success {
background-color: #d4edda;
color: #155724;
border-color: #c3e6cb;
}
small {
display: block;
}
strong {
font-weight: 600;
}
</style>