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
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:
@@ -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>
|
||||
Reference in New Issue
Block a user