Files
sam-api/app/Swagger/v1/ItemsApi.php
hskwon ddc4bb99a0 feat: 통합 품목 조회 API 및 가격 통합 시스템 구현
- 통합 품목 조회 API (materials + products UNION)
  - ItemsService, ItemsController, Swagger 문서 생성
  - 타입 필터링 (FG/PT/SM/RM/CS), 검색, 카테고리 지원
  - Collection merge 방식으로 UNION 쿼리 안정화

- 품목-가격 통합 조회
  - PricingService.getPriceByType() 추가 (SALE/PURCHASE 지원)
  - 단일 품목 조회 시 판매가/매입가 선택적 포함
  - 고객그룹 가격 우선순위 적용 및 시계열 조회

- 자재 타입 명시적 관리
  - materials.material_type 컬럼 추가 (SM/RM/CS)
  - 기존 데이터 344개 자동 변환 (RAW→RM, SUB→SM)
  - 인덱스 추가로 조회 성능 최적화

- DB 데이터 정규화
  - products.product_type: 760개 정규화 (PRODUCT→FG, PART/SUBASSEMBLY→PT)
  - 타입 코드 표준화로 API 일관성 확보

최종 데이터: 제품 760개(FG 297, PT 463), 자재 344개(SM 215, RM 129)
2025-11-11 11:30:17 +09:00

254 lines
8.2 KiB
PHP

<?php
namespace App\Swagger\v1;
use OpenApi\Annotations as OA;
/**
* @OA\Tag(name="Items", description="통합 품목 조회 (materials + products)")
*
* ========= 공용 스키마 =========
*
* 통합 품목 스키마
*
* @OA\Schema(
* schema="Item",
* type="object",
* description="통합 품목 (PRODUCT 또는 MATERIAL)",
*
* @OA\Property(property="id", type="integer", example=1),
* @OA\Property(property="item_type", type="string", enum={"PRODUCT","MATERIAL"}, example="PRODUCT", description="품목 유형"),
* @OA\Property(property="code", type="string", example="PRD-001", description="제품 코드 또는 자재 코드"),
* @OA\Property(property="name", type="string", example="스크린 모듈 KS001"),
* @OA\Property(property="unit", type="string", nullable=true, example="SET"),
* @OA\Property(property="category_id", type="integer", nullable=true, example=2),
* @OA\Property(property="type_code", type="string", example="FG", description="제품: FG/PT, 자재: SM/RM/CS"),
* @OA\Property(property="created_at", type="string", format="date-time", example="2025-01-10T10:00:00Z")
* )
*
* 가격 정보 스키마
*
* @OA\Schema(
* schema="PriceInfo",
* type="object",
* description="가격 정보 (우선순위: 고객그룹가격 → 기본가격)",
*
* @OA\Property(property="price", type="number", format="float", nullable=true, example=15000.5000, description="단가"),
* @OA\Property(property="price_history_id", type="integer", nullable=true, example=123, description="가격 이력 ID"),
* @OA\Property(property="client_group_id", type="integer", nullable=true, example=5, description="고객 그룹 ID"),
* @OA\Property(property="warning", type="string", nullable=true, example="가격 정보를 찾을 수 없습니다.", description="경고 메시지")
* )
*
* 가격 통합 품목 스키마
*
* @OA\Schema(
* schema="ItemWithPrice",
* allOf={
*
* @OA\Schema(ref="#/components/schemas/Item"),
* @OA\Schema(
* type="object",
*
* @OA\Property(
* property="prices",
* type="object",
* description="판매가/매입가 정보",
*
* @OA\Property(property="sale", ref="#/components/schemas/PriceInfo"),
* @OA\Property(property="purchase", ref="#/components/schemas/PriceInfo")
* )
* )
* }
* )
*
* 통합 품목 페이지네이션
*
* @OA\Schema(
* schema="ItemPagination",
* type="object",
*
* @OA\Property(
* property="data",
* type="array",
*
* @OA\Items(ref="#/components/schemas/Item")
* ),
*
* @OA\Property(property="current_page", type="integer", example=1),
* @OA\Property(property="last_page", type="integer", example=5),
* @OA\Property(property="per_page", type="integer", example=20),
* @OA\Property(property="total", type="integer", example=100),
* @OA\Property(property="from", type="integer", example=1),
* @OA\Property(property="to", type="integer", example=20)
* )
*/
class ItemsApi
{
/**
* 통합 품목 목록 조회
*
* @OA\Get(
* path="/api/v1/items",
* tags={"Items"},
* summary="통합 품목 목록 조회 (materials + products UNION)",
* description="제품과 자재를 UNION으로 통합하여 조회합니다. 타입 필터링, 검색, 카테고리 필터 지원.",
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(
* name="type",
* in="query",
* description="품목 타입 (쉼표 구분 또는 배열). FG=완제품, PT=부품/서브조립, SM=부자재, RM=원자재, CS=소모품",
* required=false,
*
* @OA\Schema(
* type="string",
* example="FG,PT,SM"
* )
* ),
*
* @OA\Parameter(
* name="search",
* in="query",
* description="검색어 (name, code 필드 LIKE 검색)",
* required=false,
*
* @OA\Schema(type="string", example="스크린")
* ),
*
* @OA\Parameter(
* name="category_id",
* in="query",
* description="카테고리 ID 필터",
* required=false,
*
* @OA\Schema(type="integer", example=2)
* ),
*
* @OA\Parameter(
* name="page",
* in="query",
* description="페이지 번호 (기본: 1)",
* required=false,
*
* @OA\Schema(type="integer", example=1)
* ),
*
* @OA\Parameter(
* name="size",
* in="query",
* description="페이지당 항목 수 (기본: 20)",
* required=false,
*
* @OA\Schema(type="integer", example=20)
* ),
*
* @OA\Response(
* response=200,
* description="조회 성공",
*
* @OA\JsonContent(
* allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
*
* @OA\Property(property="data", ref="#/components/schemas/ItemPagination")
* )
* }
* )
* ),
*
* @OA\Response(response=400, description="잘못된 요청", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function index() {}
/**
* 단일 품목 조회 (가격 정보 포함 가능)
*
* @OA\Get(
* path="/api/v1/items/{id}",
* tags={"Items"},
* summary="단일 품목 조회 (가격 정보 포함 가능)",
* description="PRODUCT 또는 MATERIAL 단일 품목 상세 조회. include_price=true로 판매가/매입가 포함 가능.",
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(
* name="id",
* in="path",
* required=true,
* description="품목 ID",
*
* @OA\Schema(type="integer", example=10)
* ),
*
* @OA\Parameter(
* name="item_type",
* in="query",
* required=true,
* description="품목 유형 (PRODUCT 또는 MATERIAL)",
*
* @OA\Schema(type="string", enum={"PRODUCT","MATERIAL"}, example="PRODUCT")
* ),
*
* @OA\Parameter(
* name="include_price",
* in="query",
* required=false,
* description="가격 정보 포함 여부 (기본: false)",
*
* @OA\Schema(type="boolean", example=true)
* ),
*
* @OA\Parameter(
* name="client_id",
* in="query",
* required=false,
* description="고객 ID (가격 조회 시 고객그룹 가격 우선 적용)",
*
* @OA\Schema(type="integer", example=5)
* ),
*
* @OA\Parameter(
* name="price_date",
* in="query",
* required=false,
* description="가격 기준일 (기본: 오늘)",
*
* @OA\Schema(type="string", format="date", example="2025-01-10")
* ),
*
* @OA\Response(
* response=200,
* description="조회 성공",
*
* @OA\JsonContent(
* allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
* type="object",
*
* @OA\Property(
* property="data",
* oneOf={
*
* @OA\Schema(ref="#/components/schemas/Item"),
* @OA\Schema(ref="#/components/schemas/ItemWithPrice")
* },
* description="include_price=false: Item, include_price=true: ItemWithPrice"
* )
* )
* }
* )
* ),
*
* @OA\Response(response=404, description="품목 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=400, description="잘못된 요청", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function show() {}
}