- 통합 품목 조회 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)
254 lines
8.2 KiB
PHP
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() {}
|
|
} |