Files
Lorenz Hilpert 2b5da00249 feat(checkout): add reward order confirmation feature with schema migrations
- Add new reward-order-confirmation feature library with components and store
- Implement checkout completion orchestrator service for order finalization
- Migrate checkout/oms/crm models to Zod schemas for better type safety
- Add order creation facade and display order schemas
- Update shopping cart facade with order completion flow
- Add comprehensive tests for shopping cart facade
- Update routing to include order confirmation page
2025-10-21 14:28:52 +02:00

1161 lines
27 KiB
Markdown

# @isa/utils/ean-validation
Lightweight Angular utility library for validating EAN (European Article Number) barcodes with reactive forms integration and standalone validation functions.
## Overview
The EAN Validation library provides tools for validating 13-digit EAN barcodes in Angular applications. It includes both an Angular Forms validator for reactive form validation and a standalone utility function for programmatic validation. The library uses a simple regex-based approach for fast, efficient validation without external dependencies.
## Table of Contents
- [Features](#features)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [API Reference](#api-reference)
- [Usage Examples](#usage-examples)
- [EAN Format](#ean-format)
- [Architecture Notes](#architecture-notes)
- [Testing](#testing)
- [Dependencies](#dependencies)
## Features
- **Angular Forms validator** - `eanValidator` for reactive form controls
- **Standalone validation** - `isEan()` utility function for programmatic checks
- **Regex-based validation** - Fast, efficient 13-digit EAN validation
- **Zero dependencies** - No external libraries required
- **Type-safe** - Full TypeScript support
- **Null-safe** - Graceful handling of undefined/null values
- **Lightweight** - Minimal bundle size impact
- **Framework agnostic core** - Validation logic can be used outside Angular
## Quick Start
### 1. Reactive Form Validation
```typescript
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { eanValidator } from '@isa/utils/ean-validation';
@Component({
selector: 'app-product-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="productForm">
<label>
EAN:
<input formControlName="ean" type="text" />
</label>
@if (eanControl.errors?.['invalidEan']) {
<div class="error">
Please enter a valid 13-digit EAN
</div>
}
</form>
`
})
export class ProductFormComponent {
productForm = new FormGroup({
ean: new FormControl('', [eanValidator])
});
get eanControl() {
return this.productForm.get('ean')!;
}
}
```
### 2. Programmatic Validation
```typescript
import { Component } from '@angular/core';
import { isEan } from '@isa/utils/ean-validation';
@Component({
selector: 'app-barcode-scanner',
template: `
<button (click)="validateBarcode('1234567890123')">
Validate Barcode
</button>
`
})
export class BarcodeScannerComponent {
validateBarcode(code: string): void {
if (isEan(code)) {
console.log('Valid EAN:', code);
// Process valid EAN
} else {
console.error('Invalid EAN:', code);
// Handle invalid EAN
}
}
}
```
### 3. Combined Usage
```typescript
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { eanValidator, isEan } from '@isa/utils/ean-validation';
@Component({
selector: 'app-product-search',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<input
[formControl]="searchControl"
placeholder="Enter EAN"
(keyup.enter)="search()"
/>
<button (click)="search()" [disabled]="!canSearch()">
Search
</button>
`
})
export class ProductSearchComponent {
searchControl = new FormControl('', [eanValidator]);
canSearch(): boolean {
const value = this.searchControl.value;
return !!value && isEan(value);
}
search(): void {
if (this.canSearch()) {
const ean = this.searchControl.value!;
console.log('Searching for:', ean);
// Perform search
}
}
}
```
## Core Concepts
### EAN (European Article Number)
EAN is a 13-digit barcode standard used internationally for product identification:
```
1234567890123
└───┬────┘││└─ Check digit
│ ││
│ │└── Product code
│ └─── Manufacturer code
└───────── Country/GS1 prefix
```
### Validation Rules
The library validates EANs based on these criteria:
1. **Length**: Must be exactly 13 digits
2. **Content**: Must contain only numeric characters (0-9)
3. **Format**: No spaces, dashes, or other separators allowed
**Valid EANs:**
```typescript
'1234567890123' (13 digits, all numeric)
'0000000000000' (13 digits, all numeric)
'9999999999999' (13 digits, all numeric)
```
**Invalid EANs:**
```typescript
'123456789012' (12 digits - too short)
'12345678901234' (14 digits - too long)
'123456789012A' (contains letter)
'1234-5678-9012' (contains dashes)
' 1234567890123' (contains whitespace)
'' (empty string - via isEan())
undefined (undefined - via isEan())
```
### Regex Pattern
The validation uses a simple regex pattern:
```typescript
const EAN_REGEX = /^[0-9]{13}$/;
```
**Pattern breakdown:**
- `^` - Start of string
- `[0-9]` - Any digit from 0 to 9
- `{13}` - Exactly 13 times
- `$` - End of string
## API Reference
### eanValidator
Angular Forms validator function for validating EAN input.
**Type:** `ValidatorFn`
**Signature:**
```typescript
eanValidator(control: AbstractControl): ValidationErrors | null
```
**Parameters:**
- `control: AbstractControl` - The form control to validate
**Returns:**
- `null` - If the value is valid or empty/null
- `{ invalidEan: true }` - If the value is invalid
**Validation Behavior:**
- **Empty values**: Returns `null` (considered valid - use `Validators.required` for required fields)
- **Valid EAN**: Returns `null`
- **Invalid EAN**: Returns `{ invalidEan: true }`
**Example:**
```typescript
import { FormControl } from '@angular/forms';
import { eanValidator } from '@isa/utils/ean-validation';
const eanControl = new FormControl('', [eanValidator]);
eanControl.setValue('1234567890123');
console.log(eanControl.valid); // true
eanControl.setValue('123');
console.log(eanControl.errors); // { invalidEan: true }
eanControl.setValue('');
console.log(eanControl.valid); // true (empty is valid)
```
### isEan
Standalone utility function for programmatic EAN validation.
**Signature:**
```typescript
isEan(value: string | undefined): boolean
```
**Parameters:**
- `value: string | undefined` - The value to validate
**Returns:**
- `true` - If the value is a valid 13-digit EAN
- `false` - If the value is invalid, undefined, null, or empty
**Validation Behavior:**
- **undefined/null**: Returns `false`
- **Empty string**: Returns `false`
- **Valid EAN**: Returns `true`
- **Invalid EAN**: Returns `false`
**Example:**
```typescript
import { isEan } from '@isa/utils/ean-validation';
isEan('1234567890123'); // true
isEan('123'); // false
isEan(''); // false
isEan(undefined); // false
isEan(null); // false (TypeScript allows via type coercion)
```
### EAN_REGEX
Regular expression constant for EAN validation.
**Type:** `RegExp`
**Value:** `/^[0-9]{13}$/`
**Note:** This is an internal constant. Prefer using `eanValidator` or `isEan()` for validation.
**Example:**
```typescript
import { EAN_REGEX } from '@isa/utils/ean-validation';
// Direct regex usage (not recommended)
const isValid = EAN_REGEX.test('1234567890123');
console.log(isValid); // true
// Prefer using isEan() instead
const isValidPreferred = isEan('1234567890123');
```
## Usage Examples
### Basic Form Validation
```typescript
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { eanValidator } from '@isa/utils/ean-validation';
@Component({
selector: 'app-product-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="form-field">
<label for="ean">Product EAN*</label>
<input
id="ean"
formControlName="ean"
type="text"
maxlength="13"
placeholder="1234567890123"
/>
@if (ean.touched && ean.errors) {
<div class="errors">
@if (ean.errors['required']) {
<p class="error">EAN is required</p>
}
@if (ean.errors['invalidEan']) {
<p class="error">EAN must be exactly 13 digits</p>
}
</div>
}
</div>
<button type="submit" [disabled]="!form.valid">
Save Product
</button>
</form>
`
})
export class ProductFormComponent {
form = new FormGroup({
ean: new FormControl('', [
Validators.required,
eanValidator
])
});
get ean() {
return this.form.get('ean')!;
}
onSubmit(): void {
if (this.form.valid) {
const productData = this.form.value;
console.log('Saving product:', productData);
// Save logic
}
}
}
```
### Barcode Scanner Integration
```typescript
import { Component, inject, signal } from '@angular/core';
import { isEan } from '@isa/utils/ean-validation';
interface ScanResult {
code: string;
valid: boolean;
timestamp: Date;
}
@Component({
selector: 'app-barcode-scanner',
template: `
<div class="scanner">
<h2>Barcode Scanner</h2>
<button (click)="startScanning()">
Start Scanning
</button>
<div class="scan-history">
<h3>Scan History</h3>
@for (scan of scanHistory(); track scan.timestamp) {
<div
class="scan-result"
[class.valid]="scan.valid"
[class.invalid]="!scan.valid"
>
<span>{{ scan.code }}</span>
<span>{{ scan.valid ? '✓ Valid' : '✗ Invalid' }}</span>
<span>{{ scan.timestamp | date: 'short' }}</span>
</div>
}
</div>
</div>
`
})
export class BarcodeScannerComponent {
scanHistory = signal<ScanResult[]>([]);
startScanning(): void {
// Simulated barcode scan
this.processScan('1234567890123');
}
processScan(code: string): void {
const valid = isEan(code);
this.scanHistory.update(history => [
{
code,
valid,
timestamp: new Date()
},
...history
]);
if (valid) {
console.log('Valid EAN scanned:', code);
// Process valid EAN
this.lookupProduct(code);
} else {
console.error('Invalid EAN scanned:', code);
// Show error message
}
}
lookupProduct(ean: string): void {
// Product lookup logic
}
}
```
### Search with Validation
```typescript
import { Component, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { eanValidator, isEan } from '@isa/utils/ean-validation';
@Component({
selector: 'app-product-search',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<div class="search-container">
<input
[formControl]="searchControl"
type="text"
placeholder="Enter 13-digit EAN"
maxlength="13"
(keyup.enter)="search()"
/>
<button
(click)="search()"
[disabled]="!isValidEan()"
>
Search
</button>
@if (searchControl.touched && searchControl.errors?.['invalidEan']) {
<p class="error">
Please enter a valid 13-digit EAN
</p>
}
@if (searchResult()) {
<div class="result">
<h3>Product Found</h3>
<p>EAN: {{ searchResult()!.ean }}</p>
<p>Name: {{ searchResult()!.name }}</p>
</div>
}
@if (notFound()) {
<p class="error">No product found with EAN: {{ lastSearch() }}</p>
}
</div>
`
})
export class ProductSearchComponent {
searchControl = new FormControl('', [eanValidator]);
searchResult = signal<{ ean: string; name: string } | null>(null);
notFound = signal(false);
lastSearch = signal('');
isValidEan(): boolean {
const value = this.searchControl.value;
return !!value && isEan(value);
}
search(): void {
if (!this.isValidEan()) return;
const ean = this.searchControl.value!;
this.lastSearch.set(ean);
// Simulated product lookup
const product = this.mockProductLookup(ean);
if (product) {
this.searchResult.set(product);
this.notFound.set(false);
} else {
this.searchResult.set(null);
this.notFound.set(true);
}
}
mockProductLookup(ean: string): { ean: string; name: string } | null {
// Mock implementation
return { ean, name: `Product ${ean}` };
}
}
```
### Batch Validation
```typescript
import { Component, signal } from '@angular/core';
import { isEan } from '@isa/utils/ean-validation';
interface ValidationResult {
ean: string;
valid: boolean;
}
@Component({
selector: 'app-batch-validator',
template: `
<div class="batch-validator">
<h2>Batch EAN Validator</h2>
<textarea
[value]="inputEans()"
(input)="inputEans.set($any($event.target).value)"
placeholder="Enter EANs (one per line)"
rows="10"
></textarea>
<button (click)="validate()">Validate All</button>
@if (results().length > 0) {
<div class="results">
<h3>Validation Results</h3>
<p>
Valid: {{ validCount() }} / {{ results().length }}
</p>
<table>
<thead>
<tr>
<th>EAN</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@for (result of results(); track result.ean) {
<tr [class.valid]="result.valid" [class.invalid]="!result.valid">
<td>{{ result.ean }}</td>
<td>{{ result.valid ? '✓ Valid' : '✗ Invalid' }}</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
`
})
export class BatchValidatorComponent {
inputEans = signal('');
results = signal<ValidationResult[]>([]);
validCount = signal(0);
validate(): void {
const eans = this.inputEans()
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0);
const results: ValidationResult[] = eans.map(ean => ({
ean,
valid: isEan(ean)
}));
this.results.set(results);
this.validCount.set(results.filter(r => r.valid).length);
}
}
```
### Dynamic Form with Multiple EANs
```typescript
import { Component } from '@angular/core';
import { FormArray, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { eanValidator } from '@isa/utils/ean-validation';
@Component({
selector: 'app-multi-ean-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<h3>Product EANs</h3>
<div formArrayName="eans">
@for (control of eanControls.controls; track $index) {
<div class="ean-field">
<input
[formControlName]="$index"
type="text"
maxlength="13"
placeholder="1234567890123"
/>
@if (control.touched && control.errors?.['invalidEan']) {
<span class="error">Invalid EAN</span>
}
<button type="button" (click)="removeEan($index)">
Remove
</button>
</div>
}
</div>
<button type="button" (click)="addEan()">
Add EAN
</button>
<button type="submit" [disabled]="!form.valid">
Save
</button>
</form>
`
})
export class MultiEanFormComponent {
form = new FormGroup({
eans: new FormArray([
new FormControl('', [eanValidator])
])
});
get eanControls() {
return this.form.get('eans') as FormArray;
}
addEan(): void {
this.eanControls.push(
new FormControl('', [eanValidator])
);
}
removeEan(index: number): void {
this.eanControls.removeAt(index);
}
onSubmit(): void {
if (this.form.valid) {
const eans = this.eanControls.value;
console.log('Saving EANs:', eans);
}
}
}
```
### Type Guard Usage
```typescript
import { Component } from '@angular/core';
import { isEan } from '@isa/utils/ean-validation';
interface Product {
id: number;
name: string;
ean?: string; // Optional EAN
}
@Component({
selector: 'app-product-list',
template: `
<div class="products">
@for (product of products; track product.id) {
<div class="product">
<h3>{{ product.name }}</h3>
@if (hasValidEan(product)) {
<p>EAN: {{ product.ean }}</p>
} @else {
<p class="warning">No valid EAN</p>
}
</div>
}
</div>
`
})
export class ProductListComponent {
products: Product[] = [
{ id: 1, name: 'Product A', ean: '1234567890123' },
{ id: 2, name: 'Product B', ean: '123' }, // Invalid
{ id: 3, name: 'Product C' }, // Missing
];
hasValidEan(product: Product): boolean {
return isEan(product.ean);
}
}
```
## EAN Format
### Standard Format
**EAN-13 Structure:**
```
┌──────────────────────────────────┐
│ 1 2 3 4 5 6 7 8 9 0 1 2 3 │
│ └──┬──┘ └───────┬────────┘ │ │ │
│ │ │ │ │ │
│ Prefix Manufacturer Product Check │
│ Code Code Digit │
└──────────────────────────────────┘
```
### Common Prefixes
| Prefix | Region/Type |
|--------|-------------|
| 000-019 | GS1 US |
| 020-029 | Restricted distribution |
| 030-039 | GS1 US |
| 040-049 | Restricted distribution |
| 050-059 | GS1 US reserved |
| 060-139 | GS1 US |
| 200-299 | Restricted distribution |
| 300-379 | GS1 France |
| 380 | GS1 Bulgaria |
| 383 | GS1 Slovenia |
| 385 | GS1 Croatia |
| 400-440 | GS1 Germany |
| 450-459, 490-499 | GS1 Japan |
| 460-469 | GS1 Russia |
| 470 | GS1 Kyrgyzstan |
| 471 | GS1 Taiwan |
| 474 | GS1 Estonia |
| 475 | GS1 Latvia |
| 476 | GS1 Azerbaijan |
| 477 | GS1 Lithuania |
| 478 | GS1 Uzbekistan |
| 479 | GS1 Sri Lanka |
| 480 | GS1 Philippines |
| 481 | GS1 Belarus |
| 482 | GS1 Ukraine |
| 484 | GS1 Moldova |
| 485 | GS1 Armenia |
| 486 | GS1 Georgia |
| 487 | GS1 Kazakhstan |
| 489 | GS1 Hong Kong |
| 500-509 | GS1 UK |
| 520-521 | GS1 Association Greece |
| 528 | GS1 Lebanon |
| 529 | GS1 Cyprus |
| 530 | GS1 Albania |
| 531 | GS1 Macedonia |
| 535 | GS1 Malta |
| 539 | GS1 Ireland |
| 540-549 | GS1 Belgium & Luxembourg |
| 560 | GS1 Portugal |
| 569 | GS1 Iceland |
| 570-579 | GS1 Denmark |
| 590 | GS1 Poland |
| 594 | GS1 Romania |
| 599 | GS1 Hungary |
| 600-601 | GS1 South Africa |
| 603 | GS1 Ghana |
| 604 | GS1 Senegal |
| 608 | GS1 Bahrain |
| 609 | GS1 Mauritius |
| 611 | GS1 Morocco |
| 613 | GS1 Algeria |
| 615 | GS1 Nigeria |
| 616 | GS1 Kenya |
| 618 | GS1 Ivory Coast |
| 619 | GS1 Tunisia |
| 620 | GS1 Tanzania |
| 621 | GS1 Syria |
| 622 | GS1 Egypt |
| 623 | GS1 Brunei |
| 624 | GS1 Libya |
| 625 | GS1 Jordan |
| 626 | GS1 Iran |
| 627 | GS1 Kuwait |
| 628 | GS1 Saudi Arabia |
| 629 | GS1 Emirates |
| 640-649 | GS1 Finland |
| 690-699 | GS1 China |
| 700-709 | GS1 Norway |
| 729 | GS1 Israel |
| 730-739 | GS1 Sweden |
| 740 | GS1 Guatemala |
| 741 | GS1 El Salvador |
| 742 | GS1 Honduras |
| 743 | GS1 Nicaragua |
| 744 | GS1 Costa Rica |
| 745 | GS1 Panama |
| 746 | GS1 Dominican Republic |
| 750 | GS1 Mexico |
| 754-755 | GS1 Canada |
| 759 | GS1 Venezuela |
| 760-769 | GS1 Switzerland |
| 770-771 | GS1 Colombia |
| 773 | GS1 Uruguay |
| 775 | GS1 Peru |
| 777 | GS1 Bolivia |
| 778-779 | GS1 Argentina |
| 780 | GS1 Chile |
| 784 | GS1 Paraguay |
| 786 | GS1 Ecuador |
| 789-790 | GS1 Brazil |
| 800-839 | GS1 Italy |
| 840-849 | GS1 Spain |
| 850 | GS1 Cuba |
| 858 | GS1 Slovakia |
| 859 | GS1 Czech |
| 860 | GS1 Serbia |
| 865 | GS1 Mongolia |
| 867 | GS1 North Korea |
| 868-869 | GS1 Turkey |
| 870-879 | GS1 Netherlands |
| 880 | GS1 South Korea |
| 884 | GS1 Cambodia |
| 885 | GS1 Thailand |
| 888 | GS1 Singapore |
| 890 | GS1 India |
| 893 | GS1 Vietnam |
| 896 | GS1 Pakistan |
| 899 | GS1 Indonesia |
| 900-919 | GS1 Austria |
| 930-939 | GS1 Australia |
| 940-949 | GS1 New Zealand |
| 950 | GS1 Global Office |
| 951 | GS1 Global Office (EPCglobal) |
| 955 | GS1 Malaysia |
| 958 | GS1 Macau |
| 960-969 | GS1 Global Office (GTIN-8) |
| 977 | Serial publications (ISSN) |
| 978-979 | Bookland (ISBN) |
| 980 | Refund receipts |
| 981-984 | GS1 coupon identification |
| 990-999 | GS1 coupon identification |
**Note:** This library validates format only (13 digits), not check digit accuracy or prefix validity.
## Architecture Notes
### Design Patterns
#### Validator Function Pattern
The `eanValidator` follows Angular's `ValidatorFn` pattern:
```typescript
export const eanValidator: ValidatorFn = (
control: AbstractControl
): ValidationErrors | null => {
const value = control.value;
if (value && !EAN_REGEX.test(value)) {
return { invalidEan: true };
}
return null;
};
```
**Benefits:**
- Composable with other validators
- Reusable across multiple form controls
- Type-safe with TypeScript
- Follows Angular conventions
#### Pure Function Pattern
The `isEan` function is a pure function with no side effects:
```typescript
export const isEan = (value: string | undefined): boolean => {
if (!value) {
return false;
}
return EAN_REGEX.test(value);
};
```
**Benefits:**
- Predictable - same input always produces same output
- Testable - easy to unit test
- Framework agnostic - can be used outside Angular
- No dependencies - doesn't rely on external state
### Performance Considerations
1. **Regex Performance** - Simple regex pattern for O(n) validation
2. **No External Dependencies** - Minimal bundle size impact
3. **Null-Safe** - Early returns for undefined/null values
4. **Reusable Regex** - Single compiled regex constant
### Future Enhancements
Potential improvements identified:
1. **Check Digit Validation** - Validate the EAN-13 check digit algorithm
2. **EAN-8 Support** - Support for 8-digit EAN format
3. **UPC Support** - Support for 12-digit UPC format
4. **Prefix Validation** - Validate GS1 country/region prefixes
5. **Format Conversion** - Convert between EAN-13, EAN-8, and UPC
6. **Barcode Generation** - Generate barcode images from EAN
7. **Async Validation** - Validate against product database
## Testing
The library uses **Vitest** with **Angular Testing Utilities** for testing.
### Running Tests
```bash
# Run tests for this library
npx nx test ean-validation --skip-nx-cache
# Run tests with coverage
npx nx test ean-validation --code-coverage --skip-nx-cache
# Run tests in watch mode
npx nx test ean-validation --watch
```
### Test Structure
The library includes comprehensive unit tests covering:
- **eanValidator Tests**:
- Valid 13-digit EAN returns null
- Empty value returns null (valid)
- Invalid EAN returns error object
- Null/undefined values handled correctly
- **isEan Tests**:
- Valid 13-digit EAN returns true
- Invalid EAN returns false
- Empty string returns false
- Undefined returns false
### Example Test
```typescript
import { describe, it, expect } from 'vitest';
import { FormControl } from '@angular/forms';
import { eanValidator, isEan } from './ean-validation';
describe('eanValidator', () => {
it('should return null for valid EAN', () => {
// Arrange
const control = new FormControl('1234567890123');
// Act
const result = eanValidator(control);
// Assert
expect(result).toBeNull();
});
it('should return null for empty value', () => {
// Arrange
const control = new FormControl('');
// Act
const result = eanValidator(control);
// Assert
expect(result).toBeNull();
});
it('should return error for invalid EAN', () => {
// Arrange
const control = new FormControl('123');
// Act
const result = eanValidator(control);
// Assert
expect(result).toEqual({ invalidEan: true });
});
});
describe('isEan', () => {
it('should return true for valid EAN', () => {
// Arrange
const validEan = '1234567890123';
// Act
const result = isEan(validEan);
// Assert
expect(result).toBe(true);
});
it('should return false for undefined value', () => {
// Arrange
const undefinedValue = undefined;
// Act
const result = isEan(undefinedValue);
// Assert
expect(result).toBe(false);
});
it('should return false for invalid EAN', () => {
// Arrange
const invalidEan = '123';
// Act
const result = isEan(invalidEan);
// Assert
expect(result).toBe(false);
});
});
```
## Dependencies
### Required Libraries
- `@angular/core` - For TypeScript types
- `@angular/forms` - For `ValidatorFn`, `AbstractControl`, `ValidationErrors` types
### Path Alias
Import from: `@isa/utils/ean-validation`
### Exports
- `eanValidator` - Angular Forms validator function
- `isEan` - Standalone validation utility function
- `EAN_REGEX` - Regular expression constant (internal use)
## Best Practices
### 1. Use with Validators.required
Combine with `Validators.required` for required fields:
```typescript
// Good - Required + EAN validation
const eanControl = new FormControl('', [
Validators.required,
eanValidator
]);
// Avoid - Only EAN validation (allows empty)
const eanControl = new FormControl('', [eanValidator]);
```
### 2. Provide Clear Error Messages
Show user-friendly error messages:
```typescript
// Good - Clear, actionable error message
@if (ean.errors?.['invalidEan']) {
<p class="error">
Please enter a valid 13-digit EAN (e.g., 1234567890123)
</p>
}
// Avoid - Generic or unclear message
@if (ean.errors?.['invalidEan']) {
<p class="error">Invalid</p>
}
```
### 3. Use isEan for Type Guards
Use `isEan()` for runtime type checking:
```typescript
// Good - Type-safe validation
function processEan(code: string | undefined): void {
if (isEan(code)) {
// TypeScript knows code is string here
console.log('Processing:', code.toUpperCase());
}
}
// Avoid - Unsafe type assumption
function processEan(code: string): void {
// Might throw if code is invalid
console.log('Processing:', code.toUpperCase());
}
```
### 4. Add Maxlength Attribute
Prevent users from entering more than 13 digits:
```typescript
// Good - Limit input length
<input
formControlName="ean"
type="text"
maxlength="13"
placeholder="1234567890123"
/>
// Avoid - No length restriction
<input
formControlName="ean"
type="text"
/>
```
### 5. Handle Validation in Components
Keep validation logic in components, not templates:
```typescript
// Good - Logic in component
canSubmit(): boolean {
return this.form.valid && isEan(this.form.value.ean);
}
// Avoid - Complex logic in template
@if (form.valid && form.value.ean && /^[0-9]{13}$/.test(form.value.ean)) {
...
}
```
## Limitations
### What This Library Does NOT Do
1. **Check Digit Validation** - Does not validate the EAN-13 checksum digit
2. **Database Validation** - Does not verify if EAN exists in product database
3. **Prefix Validation** - Does not validate GS1 country/region prefixes
4. **Format Conversion** - Does not convert between EAN/UPC formats
5. **Barcode Generation** - Does not generate barcode images
### When to Use Additional Validation
For production systems, consider:
- **Check digit validation** for data integrity
- **Database lookup** for product existence
- **Prefix validation** for regional compliance
- **Duplicate detection** in batch processing
## License
Internal ISA Frontend library - not for external distribution.