feat: [flow-tester] Faker 변수 바인딩 및 품목관리 API 테스트 예제 추가

- VariableBinder에 Laravel Faker 기반 랜덤 데이터 생성 기능 추가
  - {{$faker.name}}, {{$faker.email}}, {{$faker.phone}} 등 텍스트 생성
  - {{$faker.number:MIN:MAX}}, {{$faker.price:MIN:MAX}} 숫자/가격 생성
  - {{$faker.itemCode:PREFIX}}, {{$faker.code:PREFIX:LENGTH}} 코드 생성
  - {{$faker.productName}}, {{$faker.unit}}, {{$faker.category}} 품목 관련
- 가이드 모달에 Faker 변수 문서 추가
- 품목관리 API 테스트 예제 3개 추가
  - items-crud: Faker 기반 CRUD 테스트 (6단계)
  - items-search: 검색/통계 API 테스트 (5단계)
  - items-bom: BOM 관리 테스트 (8단계)
This commit is contained in:
2025-12-01 12:09:20 +09:00
parent 3d91f438ba
commit 4ede126518
3 changed files with 667 additions and 0 deletions

View File

@@ -2,6 +2,8 @@
namespace App\Services\FlowTester;
use Faker\Factory as FakerFactory;
use Faker\Generator as FakerGenerator;
use Illuminate\Support\Str;
/**
@@ -14,11 +16,14 @@
* - {{$timestamp}} - 현재 타임스탬프
* - {{$uuid}} - 랜덤 UUID
* - {{$random:N}} - N자리 랜덤 숫자
* - {{$faker.xxx}} - Faker 기반 랜덤 데이터 생성
*/
class VariableBinder
{
private array $context = [];
private ?FakerGenerator $faker = null;
/**
* 컨텍스트 초기화
*/
@@ -136,9 +141,167 @@ function ($m) {
// {{$auth.apiKey}} → .env의 API Key
$input = str_replace('{{$auth.apiKey}}', env('FLOW_TESTER_API_KEY', ''), $input);
// {{$faker.xxx}} → Faker 기반 랜덤 데이터 생성
$input = $this->resolveFaker($input);
return $input;
}
/**
* Faker 인스턴스 가져오기 (지연 초기화)
*/
private function getFaker(): FakerGenerator
{
if ($this->faker === null) {
$this->faker = FakerFactory::create('ko_KR');
}
return $this->faker;
}
/**
* Faker 변수 처리
*
* 지원 패턴:
* - {{$faker.name}} - 랜덤 이름
* - {{$faker.company}} - 랜덤 회사명
* - {{$faker.email}} - 랜덤 이메일
* - {{$faker.phone}} - 랜덤 전화번호
* - {{$faker.word}} - 랜덤 단어
* - {{$faker.sentence}} - 랜덤 문장
* - {{$faker.text}} - 랜덤 텍스트
* - {{$faker.number:MIN:MAX}} - 범위 내 랜덤 정수
* - {{$faker.price:MIN:MAX}} - 범위 내 랜덤 가격 (소수점 2자리)
* - {{$faker.code:PREFIX:LENGTH}} - 코드 생성 (PREFIX + 숫자)
* - {{$faker.itemCode:PREFIX}} - 품목코드 생성 (PREFIX + 6자리 숫자)
* - {{$faker.address}} - 랜덤 주소
* - {{$faker.url}} - 랜덤 URL
* - {{$faker.boolean}} - true/false
*/
private function resolveFaker(string $input): string
{
// {{$faker.xxx}} 또는 {{$faker.xxx:param1:param2}} 패턴 처리
return preg_replace_callback(
'/\{\{\$faker\.([a-zA-Z]+)(?::([^}]*))?\}\}/',
fn ($m) => $this->generateFakerValue($m[1], $m[2] ?? ''),
$input
);
}
/**
* Faker 값 생성
*/
private function generateFakerValue(string $type, string $params): string
{
$faker = $this->getFaker();
$paramList = $params !== '' ? explode(':', $params) : [];
return match ($type) {
// 기본 텍스트
'name' => $faker->name(),
'firstName' => $faker->firstName(),
'lastName' => $faker->lastName(),
'company' => $faker->company(),
'email' => $faker->unique()->safeEmail(),
'phone' => $faker->phoneNumber(),
'word' => $faker->word(),
'sentence' => $faker->sentence(),
'text' => $faker->text(100),
'paragraph' => $faker->paragraph(),
// 주소
'address' => $faker->address(),
'city' => $faker->city(),
'postcode' => $faker->postcode(),
// 숫자
'number' => $this->fakerNumber($faker, $paramList),
'price' => $this->fakerPrice($faker, $paramList),
'quantity' => (string) $faker->numberBetween(1, 100),
// 코드 생성
'code' => $this->fakerCode($paramList),
'itemCode' => $this->fakerItemCode($paramList),
'sku' => 'SKU-' . strtoupper($faker->bothify('??###')),
'barcode' => $faker->ean13(),
// 기타
'url' => $faker->url(),
'boolean' => $faker->boolean() ? 'true' : 'false',
'uuid' => $faker->uuid(),
'date' => $faker->date('Y-m-d'),
'datetime' => $faker->dateTime()->format('Y-m-d H:i:s'),
// 품목 관련
'productName' => $this->fakerProductName($faker),
'unit' => $faker->randomElement(['EA', 'SET', 'BOX', 'KG', 'M', 'L', 'PCS']),
'category' => $faker->randomElement(['원자재', '부품', '반제품', '완제품', '소모품']),
default => "{{faker.{$type}}}",
};
}
/**
* 범위 내 랜덤 정수 생성
*/
private function fakerNumber(FakerGenerator $faker, array $params): string
{
$min = isset($params[0]) ? (int) $params[0] : 1;
$max = isset($params[1]) ? (int) $params[1] : 1000;
return (string) $faker->numberBetween($min, $max);
}
/**
* 범위 내 랜덤 가격 생성 (소수점 2자리)
*/
private function fakerPrice(FakerGenerator $faker, array $params): string
{
$min = isset($params[0]) ? (float) $params[0] : 1000;
$max = isset($params[1]) ? (float) $params[1] : 100000;
return number_format($faker->randomFloat(2, $min, $max), 2, '.', '');
}
/**
* 커스텀 코드 생성 (PREFIX + 숫자)
*/
private function fakerCode(array $params): string
{
$prefix = $params[0] ?? 'CODE';
$length = isset($params[1]) ? (int) $params[1] : 6;
$digits = str_pad((string) random_int(0, (int) pow(10, $length) - 1), $length, '0', STR_PAD_LEFT);
return $prefix . $digits;
}
/**
* 품목코드 생성 (PREFIX + 6자리 숫자)
*/
private function fakerItemCode(array $params): string
{
$prefix = $params[0] ?? 'ITEM';
$digits = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
return $prefix . '-' . $digits;
}
/**
* 제품명 생성 (한글)
*/
private function fakerProductName(FakerGenerator $faker): string
{
$prefixes = ['고급', '프리미엄', '스탠다드', '베이직', '프로', '울트라'];
$types = ['부품', '자재', '모듈', '키트', '세트', '패키지'];
$models = ['A', 'B', 'C', 'X', 'Y', 'Z', 'Pro', 'Plus', 'Max'];
return $faker->randomElement($prefixes) . ' '
. $faker->randomElement($types) . ' '
. $faker->randomElement($models) . '-'
. $faker->numberBetween(100, 999);
}
/**
* 현재 로그인 사용자의 API 토큰 조회
*/

View File

@@ -39,6 +39,15 @@ class="text-gray-400 hover:text-gray-600 transition-colors">
<button type="button" class="example-tab px-4 py-2 text-sm font-medium rounded-lg transition-colors" data-example="auth-flow">
인증 플로우
</button>
<button type="button" class="example-tab px-4 py-2 text-sm font-medium rounded-lg transition-colors bg-green-100 text-green-800" data-example="items-crud">
품목 CRUD (Faker)
</button>
<button type="button" class="example-tab px-4 py-2 text-sm font-medium rounded-lg transition-colors bg-green-100 text-green-800" data-example="items-search">
품목 검색/통계
</button>
<button type="button" class="example-tab px-4 py-2 text-sm font-medium rounded-lg transition-colors bg-green-100 text-green-800" data-example="items-bom">
품목 BOM 관리
</button>
</div>
</div>
@@ -90,6 +99,57 @@ class="absolute top-2 right-2 px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 te
</div>
</div>
{{-- 품목 CRUD (Faker) --}}
<div id="example-items-crud" class="example-content hidden">
<div class="mb-4">
<h3 class="text-lg font-semibold text-gray-800">품목 CRUD 테스트 - Faker 활용 (6 스텝)</h3>
<p class="text-sm text-gray-600 mt-1">Faker 변수로 랜덤 데이터 생성 - 품목 생성 - 조회 - 수정 - 삭제</p>
<div class="mt-2 p-2 bg-green-50 border border-green-200 rounded text-xs text-green-800">
<strong>Faker 활용:</strong> <code>@{{$faker.itemCode:TEST}}</code>, <code>@{{$faker.productName}}</code>, <code>@{{$faker.price:1000:50000}}</code>
</div>
</div>
<div class="relative">
<button onclick="ExamplesModal.copy('items-crud')"
class="absolute top-2 right-2 px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 text-white rounded transition-colors z-10">
복사
</button>
<pre id="json-items-crud" class="bg-gray-800 text-gray-100 p-4 rounded-lg text-xs overflow-x-auto max-h-[60vh]"></pre>
</div>
</div>
{{-- 품목 검색/통계 --}}
<div id="example-items-search" class="example-content hidden">
<div class="mb-4">
<h3 class="text-lg font-semibold text-gray-800">품목 검색/통계 테스트 (5 스텝)</h3>
<p class="text-sm text-gray-600 mt-1">목록 조회 - 검색 (키워드/필터) - 통계 조회</p>
</div>
<div class="relative">
<button onclick="ExamplesModal.copy('items-search')"
class="absolute top-2 right-2 px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 text-white rounded transition-colors z-10">
복사
</button>
<pre id="json-items-search" class="bg-gray-800 text-gray-100 p-4 rounded-lg text-xs overflow-x-auto max-h-[60vh]"></pre>
</div>
</div>
{{-- 품목 BOM 관리 --}}
<div id="example-items-bom" class="example-content hidden">
<div class="mb-4">
<h3 class="text-lg font-semibold text-gray-800">품목 BOM 관리 테스트 (8 스텝)</h3>
<p class="text-sm text-gray-600 mt-1">부모 품목 생성 - 자재 생성 - BOM 등록 - BOM 조회 - BOM 수정 - 정리</p>
<div class="mt-2 p-2 bg-blue-50 border border-blue-200 rounded text-xs text-blue-800">
<strong>BOM:</strong> Bill of Materials - 제품 구성 자재 목록 관리
</div>
</div>
<div class="relative">
<button onclick="ExamplesModal.copy('items-bom')"
class="absolute top-2 right-2 px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 text-white rounded transition-colors z-10">
복사
</button>
<pre id="json-items-bom" class="bg-gray-800 text-gray-100 p-4 rounded-lg text-xs overflow-x-auto max-h-[60vh]"></pre>
</div>
</div>
</div>
{{-- 모달 푸터 --}}
@@ -398,6 +458,349 @@ class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-
"expect": { "status": [200, 204] }
}
]
},
'items-crud': {
"version": "1.0",
"meta": {
"name": "품목 CRUD 테스트 (Faker)",
"description": "Faker 변수를 활용한 품목 생성/조회/수정/삭제 테스트",
"tags": ["items", "crud", "faker"]
},
"config": {
"baseUrl": "https://sam.kr/api/v1",
"timeout": 30000,
"stopOnFailure": false,
"headers": {
"Accept": "application/json",
"Content-Type": "application/json"
}
},
"variables": {},
"steps": [
{
"id": "create_item",
"name": "[품목] Faker로 생성",
"description": "Faker 변수를 사용해 랜덤 품목 데이터 생성",
"method": "POST",
"endpoint": "/items",
"body": {
"code": "{{$faker.itemCode:TEST}}",
"name": "{{$faker.productName}}",
"item_type": "PRODUCT",
"unit": "{{$faker.unit}}",
"unit_price": "{{$faker.price:1000:50000}}",
"description": "{{$faker.sentence}}",
"is_active": true
},
"expect": {
"status": [200, 201],
"jsonPath": {
"$.success": true,
"$.data.code": "@isString",
"$.data.name": "@notEmpty"
}
},
"extract": {
"itemCode": "$.data.code",
"itemId": "$.data.id",
"itemName": "$.data.name"
}
},
{
"id": "get_item_by_code",
"name": "[품목] 코드로 조회",
"dependsOn": ["create_item"],
"method": "GET",
"endpoint": "/items/code/{{create_item.itemCode}}",
"expect": {
"status": [200],
"jsonPath": {
"$.success": true,
"$.data.code": "{{create_item.itemCode}}"
}
}
},
{
"id": "update_item",
"name": "[품목] 수정 (코드 기반)",
"dependsOn": ["get_item_by_code"],
"method": "PUT",
"endpoint": "/items/{{create_item.itemCode}}",
"body": {
"name": "{{$faker.productName}} (수정됨)",
"unit_price": "{{$faker.price:50000:100000}}",
"description": "수정된 설명 - {{$faker.sentence}}"
},
"expect": {
"status": [200],
"jsonPath": {
"$.success": true
}
}
},
{
"id": "verify_update",
"name": "[품목] 수정 확인",
"dependsOn": ["update_item"],
"method": "GET",
"endpoint": "/items/code/{{create_item.itemCode}}",
"expect": {
"status": [200],
"jsonPath": {
"$.data.description": "@contains:수정된 설명"
}
}
},
{
"id": "delete_item",
"name": "[품목] 삭제 (코드 기반)",
"dependsOn": ["verify_update"],
"method": "DELETE",
"endpoint": "/items/{{create_item.itemCode}}",
"expect": {
"status": [200, 204]
}
},
{
"id": "verify_delete",
"name": "[품목] 삭제 확인",
"dependsOn": ["delete_item"],
"method": "GET",
"endpoint": "/items/code/{{create_item.itemCode}}",
"expect": {
"status": [404]
},
"continueOnFailure": true
}
]
},
'items-search': {
"version": "1.0",
"meta": {
"name": "품목 검색/통계 테스트",
"description": "품목 목록 조회, 검색, 통계 API 테스트",
"tags": ["items", "search", "stats"]
},
"config": {
"baseUrl": "https://sam.kr/api/v1",
"timeout": 30000,
"stopOnFailure": false
},
"variables": {
"searchKeyword": "부품"
},
"steps": [
{
"id": "list_items",
"name": "[목록] 전체 품목 조회",
"method": "GET",
"endpoint": "/items",
"expect": {
"status": [200],
"jsonPath": {
"$.success": true,
"$.data": "@isArray"
}
},
"extract": {
"totalCount": "$.meta.total"
}
},
{
"id": "list_items_paginated",
"name": "[목록] 페이지네이션",
"dependsOn": ["list_items"],
"method": "GET",
"endpoint": "/items?page=1&per_page=10",
"expect": {
"status": [200],
"jsonPath": {
"$.success": true,
"$.meta.per_page": 10
}
}
},
{
"id": "search_by_keyword",
"name": "[검색] 키워드 검색",
"dependsOn": ["list_items_paginated"],
"method": "GET",
"endpoint": "/items/search?keyword={{variables.searchKeyword}}",
"expect": {
"status": [200],
"jsonPath": {
"$.success": true
}
},
"extract": {
"searchCount": "$.meta.total"
}
},
{
"id": "search_by_type",
"name": "[검색] 품목유형 필터",
"dependsOn": ["search_by_keyword"],
"method": "GET",
"endpoint": "/items/search?item_type=PRODUCT",
"expect": {
"status": [200],
"jsonPath": {
"$.success": true
}
}
},
{
"id": "get_stats",
"name": "[통계] 품목 통계 조회",
"dependsOn": ["search_by_type"],
"method": "GET",
"endpoint": "/items/stats",
"expect": {
"status": [200],
"jsonPath": {
"$.success": true
}
},
"extract": {
"productCount": "$.data.by_type.PRODUCT",
"materialCount": "$.data.by_type.MATERIAL"
}
}
]
},
'items-bom': {
"version": "1.0",
"meta": {
"name": "품목 BOM 관리 테스트",
"description": "BOM(Bill of Materials) 등록/조회/수정/삭제 테스트",
"tags": ["items", "bom", "integration"]
},
"config": {
"baseUrl": "https://sam.kr/api/v1",
"timeout": 30000,
"stopOnFailure": false
},
"variables": {},
"steps": [
{
"id": "create_parent",
"name": "[부모품목] 완제품 생성",
"method": "POST",
"endpoint": "/items",
"body": {
"code": "{{$faker.itemCode:PROD}}",
"name": "{{$faker.productName}}",
"item_type": "PRODUCT",
"unit": "EA",
"unit_price": "{{$faker.price:100000:500000}}"
},
"expect": { "status": [200, 201] },
"extract": {
"parentCode": "$.data.code",
"parentId": "$.data.id"
}
},
{
"id": "create_child1",
"name": "[자재1] 원자재 생성",
"dependsOn": ["create_parent"],
"method": "POST",
"endpoint": "/items",
"body": {
"code": "{{$faker.itemCode:MAT}}",
"name": "원자재 - {{$faker.word}}",
"item_type": "MATERIAL",
"unit": "{{$faker.unit}}",
"unit_price": "{{$faker.price:1000:10000}}"
},
"expect": { "status": [200, 201] },
"extract": {
"child1Code": "$.data.code",
"child1Id": "$.data.id"
}
},
{
"id": "create_child2",
"name": "[자재2] 부품 생성",
"dependsOn": ["create_child1"],
"method": "POST",
"endpoint": "/items",
"body": {
"code": "{{$faker.itemCode:PART}}",
"name": "부품 - {{$faker.word}}",
"item_type": "MATERIAL",
"unit": "EA",
"unit_price": "{{$faker.price:5000:20000}}"
},
"expect": { "status": [200, 201] },
"extract": {
"child2Code": "$.data.code",
"child2Id": "$.data.id"
}
},
{
"id": "add_bom1",
"name": "[BOM] 원자재 등록",
"dependsOn": ["create_child2"],
"method": "POST",
"endpoint": "/items/{{create_parent.parentCode}}/bom",
"body": {
"child_code": "{{create_child1.child1Code}}",
"quantity": "{{$faker.number:1:10}}",
"unit": "{{$faker.unit}}"
},
"expect": { "status": [200, 201] }
},
{
"id": "add_bom2",
"name": "[BOM] 부품 등록",
"dependsOn": ["add_bom1"],
"method": "POST",
"endpoint": "/items/{{create_parent.parentCode}}/bom",
"body": {
"child_code": "{{create_child2.child2Code}}",
"quantity": "{{$faker.number:1:5}}",
"unit": "EA"
},
"expect": { "status": [200, 201] }
},
{
"id": "get_bom",
"name": "[BOM] 조회",
"dependsOn": ["add_bom2"],
"method": "GET",
"endpoint": "/items/{{create_parent.parentCode}}/bom",
"expect": {
"status": [200],
"jsonPath": {
"$.success": true,
"$.data": "@isArray",
"$.data": "@minLength:2"
}
}
},
{
"id": "cleanup_parent",
"name": "[정리] 부모품목 삭제",
"dependsOn": ["get_bom"],
"method": "DELETE",
"endpoint": "/items/{{create_parent.parentCode}}",
"expect": { "status": [200, 204] },
"continueOnFailure": true
},
{
"id": "cleanup_children",
"name": "[정리] 자재 삭제",
"dependsOn": ["cleanup_parent"],
"method": "DELETE",
"endpoint": "/items/batch",
"body": {
"codes": ["{{create_child1.child1Code}}", "{{create_child2.child2Code}}"]
},
"expect": { "status": [200, 204] },
"continueOnFailure": true
}
]
}
};

View File

@@ -166,6 +166,107 @@ class="text-gray-400 hover:text-gray-600 transition-colors">
</tbody>
</table>
<h4 class="text-lg font-semibold mt-6 mb-3">Faker 변수 (랜덤 데이터 생성)</h4>
<p class="text-gray-600 mb-3 text-sm">Create API 테스트 임의의 테스트 데이터를 자동 생성합니다. <code class="bg-gray-100 px-1 rounded">@{{$faker.타입}}</code> 형식으로 사용합니다.</p>
<table class="w-full text-sm border border-gray-200 rounded-lg overflow-hidden">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-2 text-left">패턴</th>
<th class="px-4 py-2 text-left">설명</th>
<th class="px-4 py-2 text-left">예시 결과</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr class="bg-blue-50">
<td colspan="3" class="px-4 py-2 font-semibold text-blue-800">텍스트</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.name}}</code></td>
<td class="px-4 py-2">랜덤 이름</td>
<td class="px-4 py-2">김철수</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.company}}</code></td>
<td class="px-4 py-2">랜덤 회사명</td>
<td class="px-4 py-2">삼성전자</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.email}}</code></td>
<td class="px-4 py-2">랜덤 이메일</td>
<td class="px-4 py-2">test@example.com</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.phone}}</code></td>
<td class="px-4 py-2">랜덤 전화번호</td>
<td class="px-4 py-2">010-1234-5678</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.sentence}}</code></td>
<td class="px-4 py-2">랜덤 문장</td>
<td class="px-4 py-2">테스트용 설명 문장입니다.</td>
</tr>
<tr class="bg-green-50">
<td colspan="3" class="px-4 py-2 font-semibold text-green-800">숫자/가격</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.number:MIN:MAX}}</code></td>
<td class="px-4 py-2">범위 정수</td>
<td class="px-4 py-2"><code class="text-xs">@{{$faker.number:1:100}}</code> 42</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.price:MIN:MAX}}</code></td>
<td class="px-4 py-2">범위 가격</td>
<td class="px-4 py-2"><code class="text-xs">@{{$faker.price:1000:50000}}</code> 25430.50</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.quantity}}</code></td>
<td class="px-4 py-2">수량 (1~100)</td>
<td class="px-4 py-2">25</td>
</tr>
<tr class="bg-purple-50">
<td colspan="3" class="px-4 py-2 font-semibold text-purple-800">코드/ID 생성</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.itemCode:PREFIX}}</code></td>
<td class="px-4 py-2">품목코드 생성</td>
<td class="px-4 py-2"><code class="text-xs">@{{$faker.itemCode:TEST}}</code> TEST-482916</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.code:PREFIX:LEN}}</code></td>
<td class="px-4 py-2">커스텀 코드</td>
<td class="px-4 py-2"><code class="text-xs">@{{$faker.code:MAT:4}}</code> MAT0042</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.sku}}</code></td>
<td class="px-4 py-2">SKU 코드</td>
<td class="px-4 py-2">SKU-AB123</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.barcode}}</code></td>
<td class="px-4 py-2">바코드 (EAN13)</td>
<td class="px-4 py-2">4006381333931</td>
</tr>
<tr class="bg-orange-50">
<td colspan="3" class="px-4 py-2 font-semibold text-orange-800">품목 관련</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.productName}}</code></td>
<td class="px-4 py-2">제품명 (한글)</td>
<td class="px-4 py-2">프리미엄 부품 Pro-425</td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.unit}}</code></td>
<td class="px-4 py-2">단위</td>
<td class="px-4 py-2">EA, SET, BOX, KG </td>
</tr>
<tr>
<td class="px-4 py-2"><code class="bg-gray-100 px-1 rounded text-xs">@{{$faker.category}}</code></td>
<td class="px-4 py-2">품목 카테고리</td>
<td class="px-4 py-2">원자재, 부품, 반제품 </td>
</tr>
</tbody>
</table>
<h4 class="text-lg font-semibold mt-6 mb-3">사용 위치</h4>
<ul class="list-disc list-inside space-y-2 text-gray-700">
<li><strong>endpoint:</strong> <code class="bg-gray-100 px-1 rounded text-xs">/pages/@{{step1.pageId}}/sections</code></li>