> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.vpms.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 재고 관리 (Inventory)

> 객실 일별 재고 관리 API

## 개요

Core 서비스의 재고 관리 도메인은 객실 타입별 일별 재고 현황을 관리합니다. 재고 조회, 가용성 확인, 변경 이력 추적 기능을 제공하여 예약 가능한 객실 수를 실시간으로 파악할 수 있습니다.

## 재고 구성 요소

재고는 다음과 같이 구성됩니다:

* **totalCount**: 전체 객실 수
* **usageCount**: 사용 중인 객실 수 (확정 예약)
* **holdingCount**: 가예약(TENTATIVE/INQUIRY) 재고 버킷
* **forcedAssignCount**: 강제 배정 객실 수 (`ReservationNight.roomTypeId ≠ reservation.intendedRoomTypeId` 인 활성 박)
* **blockedCount**: 차단된 객실 수 (판매 중지)
* **maintenanceCount**: 유지보수 중인 객실 수
* **availableCount**: 가용 객실 수 (자동 계산)

### 가용 객실 계산

```
availableCount = totalCount - usageCount - holdingCount - blockedCount - maintenanceCount
```

### 영업일(Cutoff) 정책

재고 보정/이동 시 과거 영업일은 동결(freeze)합니다.

* **cutoffDate** = `auditToday`(일마감 사용 시) 또는 업장 로컬 오늘
* `date < cutoffDate` 구간은 usage/total을 변경하지 않음
* `date >= cutoffDate` 구간만 변경 반영

예시(오늘 8/4):

* 체크인일을 8/3 → 8/1로 당겨도 8/1\~8/3 과거 구간은 backfill 하지 않음
* 객실 재배정/객실타입 변경 시 8/4 이후 날짜부터만 재고 이동

## Types

### RoomTypeDailyInventory

특정 객실 타입의 일별 재고 정보

<ResponseField name="id" type="ID!">
  재고 고유 식별자
</ResponseField>

<ResponseField name="accommodationId" type="ID!">
  숙소 ID
</ResponseField>

<ResponseField name="roomTypeId" type="ID!">
  객실 타입 ID
</ResponseField>

<ResponseField name="date" type="String!">
  대상 날짜 (YYYY-MM-DD 형식)
</ResponseField>

<ResponseField name="totalCount" type="Int!">
  전체 객실 수
</ResponseField>

<ResponseField name="usageCount" type="Int!">
  사용 중인 객실 수
</ResponseField>

<ResponseField name="holdingCount" type="Int!">
  가예약(TENTATIVE/INQUIRY) 재고 버킷. 강제 배정 수와 무관.
</ResponseField>

<ResponseField name="forcedAssignCount" type="Int!">
  강제 배정 객실 수. 해당 일자·객실타입 기준 `ReservationNight.roomTypeId ≠ reservation.intendedRoomTypeId` 인 활성 박 수. availableCount 계산에는 포함되지 않음.
</ResponseField>

<ResponseField name="blockedCount" type="Int!">
  차단된 객실 수
</ResponseField>

<ResponseField name="maintenanceCount" type="Int!">
  유지보수 중인 객실 수
</ResponseField>

<ResponseField name="availableCount" type="Int!">
  가용 객실 수 (계산된 필드)
</ResponseField>

<ResponseField name="version" type="Int!">
  낙관적 락을 위한 버전 번호
</ResponseField>

<ResponseField name="roomType" type="RoomType">
  연관된 객실 타입 정보
</ResponseField>

### RoomTypeDailyInventoryHistory

재고 변경 이력

<ResponseField name="id" type="ID!">
  이력 고유 식별자
</ResponseField>

<ResponseField name="inventoryId" type="ID!">
  재고 ID
</ResponseField>

<ResponseField name="action" type="InventoryAction!">
  재고 변경 액션: `CREATE`, `USE`, `RELEASE`, `HOLD`, `UNHOLD`, `BLOCK`, `UNBLOCK`, `MAINTENANCE`, `ADJUST`
</ResponseField>

<ResponseField name="previousState" type="InventoryState!">
  변경 전 재고 상태
</ResponseField>

<ResponseField name="newState" type="InventoryState!">
  변경 후 재고 상태
</ResponseField>

<ResponseField name="delta" type="InventoryState!">
  변경 차이값
</ResponseField>

<ResponseField name="referenceType" type="InventoryReferenceType">
  참조 타입: `RESERVATION`, `BLOCK`, `MAINTENANCE`, `MANUAL`
</ResponseField>

<ResponseField name="referenceId" type="ID">
  참조 ID (예약 ID, 차단 ID 등)
</ResponseField>

<ResponseField name="performedBy" type="String!">
  변경 수행자
</ResponseField>

<ResponseField name="reason" type="String">
  변경 사유
</ResponseField>

<ResponseField name="metadata" type="JSON">
  추가 메타데이터
</ResponseField>

### InventoryState

재고 상태 정보

<ResponseField name="usageCount" type="Int!">
  사용 수량
</ResponseField>

<ResponseField name="holdingCount" type="Int!">
  보류 수량
</ResponseField>

<ResponseField name="blockedCount" type="Int!">
  차단 수량
</ResponseField>

<ResponseField name="maintenanceCount" type="Int!">
  유지보수 수량
</ResponseField>

## Queries

### roomTypeDailyInventory

특정 날짜의 객실 재고를 조회합니다.

#### GraphQL Signature

```graphql theme={null}
query RoomTypeDailyInventory(
  $accommodationId: ID!
  $roomTypeId: ID!
  $date: String!
) {
  roomTypeDailyInventory(
    accommodationId: $accommodationId
    roomTypeId: $roomTypeId
    date: $date
  ) {
    id
    date
    totalCount
    usageCount
    holdingCount
    forcedAssignCount
    blockedCount
    maintenanceCount
    availableCount
    roomType {
      id
      name
    }
  }
}
```

#### 파라미터

<ParamField path="accommodationId" type="ID!" required>
  숙소 ID
</ParamField>

<ParamField path="roomTypeId" type="ID!" required>
  객실 타입 ID
</ParamField>

<ParamField path="date" type="String!" required>
  조회할 날짜 (YYYY-MM-DD 형식)
</ParamField>

#### 응답

<ResponseField name="roomTypeDailyInventory" type="RoomTypeDailyInventory">
  해당 날짜의 재고 정보. 재고가 없으면 `null` 반환
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  query {
    roomTypeDailyInventory(
      accommodationId: "01HQK..."
      roomTypeId: "01HQL..."
      date: "2025-12-25"
    ) {
      id
      date
      totalCount
      availableCount
      roomType {
        name
      }
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "roomTypeDailyInventory": {
        "id": "01HQM...",
        "date": "2025-12-25",
        "totalCount": 10,
        "availableCount": 7,
        "roomType": {
          "name": "디럭스 더블"
        }
      }
    }
  }
  ```
</CodeGroup>

***

### roomTypeDailyInventories

날짜 범위의 객실 재고 목록을 조회합니다.

#### GraphQL Signature

```graphql theme={null}
query RoomTypeDailyInventories(
  $accommodationId: ID!
  $roomTypeId: ID
  $startDate: String!
  $endDate: String!
) {
  roomTypeDailyInventories(
    accommodationId: $accommodationId
    roomTypeId: $roomTypeId
    startDate: $startDate
    endDate: $endDate
  ) {
    id
    date
    roomTypeId
    totalCount
    availableCount
    roomType {
      id
      name
    }
  }
}
```

#### 파라미터

<ParamField path="accommodationId" type="ID!" required>
  숙소 ID
</ParamField>

<ParamField path="roomTypeId" type="ID">
  객실 타입 ID (선택). 미제공 시 모든 객실 타입의 재고 조회
</ParamField>

<ParamField path="startDate" type="String!" required>
  시작 날짜 (YYYY-MM-DD 형식, 포함)
</ParamField>

<ParamField path="endDate" type="String!" required>
  종료 날짜 (YYYY-MM-DD 형식, 포함)
</ParamField>

#### 응답

<ResponseField name="roomTypeDailyInventories" type="[RoomTypeDailyInventory!]!">
  날짜 범위 내 재고 목록 (날짜 오름차순 정렬)
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  query {
    roomTypeDailyInventories(
      accommodationId: "01HQK..."
      roomTypeId: "01HQL..."
      startDate: "2025-12-25"
      endDate: "2025-12-27"
    ) {
      date
      totalCount
      usageCount
      availableCount
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "roomTypeDailyInventories": [
        {
          "date": "2025-12-25",
          "totalCount": 10,
          "usageCount": 3,
          "availableCount": 7
        },
        {
          "date": "2025-12-26",
          "totalCount": 10,
          "usageCount": 5,
          "availableCount": 5
        },
        {
          "date": "2025-12-27",
          "totalCount": 10,
          "usageCount": 2,
          "availableCount": 8
        }
      ]
    }
  }
  ```
</CodeGroup>

<Info>
  재고가 없는 날짜는 결과에 포함되지 않습니다. 재고 데이터는 사전에 생성되어야 합니다.
</Info>

***

### inventoryHistory

재고 변경 이력을 조회합니다.

#### GraphQL Signature

```graphql theme={null}
query InventoryHistory($inventoryId: ID!, $limit: Int) {
  inventoryHistory(inventoryId: $inventoryId, limit: $limit) {
    id
    action
    previousState {
      usageCount
      holdingCount
      blockedCount
      maintenanceCount
    }
    newState {
      usageCount
      holdingCount
      blockedCount
      maintenanceCount
    }
    delta {
      usageCount
      holdingCount
      blockedCount
      maintenanceCount
    }
    referenceType
    referenceId
    performedBy
    reason
    createdAt
  }
}
```

#### 파라미터

<ParamField path="inventoryId" type="ID!" required>
  재고 ID
</ParamField>

<ParamField path="limit" type="Int">
  조회할 이력 개수 (기본값: 50)
</ParamField>

#### 응답

<ResponseField name="inventoryHistory" type="[RoomTypeDailyInventoryHistory!]!">
  재고 변경 이력 목록 (최신순 정렬)
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  query {
    inventoryHistory(
      inventoryId: "01HQM..."
      limit: 10
    ) {
      action
      previousState {
        usageCount
      }
      newState {
        usageCount
      }
      delta {
        usageCount
      }
      referenceType
      performedBy
      reason
      createdAt
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "inventoryHistory": [
        {
          "action": "USE",
          "previousState": {
            "usageCount": 2
          },
          "newState": {
            "usageCount": 3
          },
          "delta": {
            "usageCount": 1
          },
          "referenceType": "RESERVATION",
          "performedBy": "system",
          "reason": "예약 확정",
          "createdAt": "2025-12-23T10:30:00Z"
        }
      ]
    }
  }
  ```
</CodeGroup>

#### 에러 처리

<ResponseField name="DATA_NOT_FOUND_ON_ID">
  존재하지 않는 재고 ID입니다.
</ResponseField>

***

### availableInventoryCount

날짜 범위에서 예약 가능한 최소 객실 수를 확인합니다.

#### GraphQL Signature

```graphql theme={null}
query AvailableInventoryCount(
  $accommodationId: ID!
  $roomTypeId: ID!
  $startDate: String!
  $endDate: String!
) {
  availableInventoryCount(
    accommodationId: $accommodationId
    roomTypeId: $roomTypeId
    startDate: $startDate
    endDate: $endDate
  )
}
```

#### 파라미터

<ParamField path="accommodationId" type="ID!" required>
  숙소 ID
</ParamField>

<ParamField path="roomTypeId" type="ID!" required>
  객실 타입 ID
</ParamField>

<ParamField path="startDate" type="String!" required>
  체크인 날짜 (YYYY-MM-DD 형식, 포함)
</ParamField>

<ParamField path="endDate" type="String!" required>
  체크아웃 날짜 (YYYY-MM-DD 형식, 포함)
</ParamField>

#### 응답

<ResponseField name="availableInventoryCount" type="Int!">
  날짜 범위 내 최소 가용 재고 수량

  * 재고가 없으면 0 반환
  * 음수는 0으로 처리
</ResponseField>

#### 동작 방식

이 API는 숙박 기간 동안 **병목(bottleneck) 날짜**를 찾아 예약 가능한 최대 객실 수를 반환합니다.

예시:

* 12월 25일: 가용 7개
* 12월 26일: 가용 3개 (병목)
* 12월 27일: 가용 8개

결과: **3개** (최소값)

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  query {
    availableInventoryCount(
      accommodationId: "01HQK..."
      roomTypeId: "01HQL..."
      startDate: "2025-12-25"
      endDate: "2025-12-27"
    )
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "availableInventoryCount": 3
    }
  }
  ```
</CodeGroup>

<Tip>
  예약 가능 여부 확인 시 이 API를 사용하세요. 숙박 기간 중 하루라도 재고가 부족하면 예약이 불가능합니다.
</Tip>

## 사용 흐름

### 예약 가능 여부 확인

1. **가용 재고 조회**: `availableInventoryCount`로 예약 가능한 최대 객실 수 확인
2. **재고 확인**: 요청 객실 수 ≤ 가용 재고 수인지 검증
3. **예약 진행**: 가능하면 예약 프로세스 진행

### 재고 현황 모니터링

1. **범위 조회**: `roomTypeDailyInventories`로 특정 기간 재고 조회
2. **상세 확인**: 특정 날짜의 상세 정보는 `roomTypeDailyInventory`로 조회
3. **이력 추적**: `inventoryHistory`로 재고 변경 이력 확인

## 권한 요구사항

모든 재고 API는 해당 숙소에 대한 관리 권한(`isManageableAccommodation`)이 필요합니다.

## 관련 API

* [객실 타입 API](/api-reference/core-svc/room) - 객실 타입 정보 관리
* [예약 API](/api-reference/core-svc/reservation) - 재고를 사용하는 예약 생성
* [ARI Rate API](/api-reference/core-svc/ari-rate) - 가격 및 판매 가능 여부 관리
