> ## 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.

# 청구서 관리 (Bill)

> 숙박 시설의 청구서 및 결제 관리 API

## 개요

Bill 도메인은 숙박 시설에서 고객에게 발행되는 청구서(Bill)와 결제(Payment) 정보를 관리합니다. Folio와 연결되어 요금, 서비스 비용, 추가 비용 등을 청구하고, 결제 내역을 추적합니다.

## 주요 기능

* **청구서 생성 및 관리**: Folio에 청구서 추가
* **결제 처리**: 청구서에 대한 결제 기록 관리
* **청구서 이전**: 다른 Folio로 청구서 이전 (Transfer)
* **청구서 정산**: 동일 Folio 내 청구서 간 정산 (Settlement)
* **청구서 취소**: 청구서 및 결제 취소 처리

## Queries

### getAccommodationBills

숙박 시설의 모든 청구서 목록을 조회합니다.

#### GraphQL Signature

```graphql theme={null}
query GetAccommodationBills($accommodationId: ID!) {
  getAccommodationBills(accommodationId: $accommodationId) {
    id
    billCode
    folioId
    reservationId
    billName
    totalAmount
    balanceAmount
    status
    transferOriginFolioId
    createdAt
    createdBy
  }
}
```

#### 파라미터

<ParamField path="accommodationId" type="ID!" required>
  숙박 시설 ID
</ParamField>

#### 응답

<ResponseField name="id" type="ID!">
  청구서의 고유 식별자 (ULID 형식)
</ResponseField>

<ResponseField name="billCode" type="String!">
  청구서 고유 코드 (자동 생성)
</ResponseField>

<ResponseField name="folioId" type="ID!">
  청구서가 속한 Folio ID
</ResponseField>

<ResponseField name="reservationId" type="ID">
  연결된 예약 ID (선택사항)
</ResponseField>

<ResponseField name="billName" type="String!">
  청구서 이름
</ResponseField>

<ResponseField name="totalAmount" type="Decimal!">
  총 청구 금액
</ResponseField>

<ResponseField name="balanceAmount" type="Decimal!">
  남은 잔액 (미결제 금액)
</ResponseField>

<ResponseField name="status" type="String">
  청구서 상태
</ResponseField>

<ResponseField name="transferOriginFolioId" type="ID">
  이전된 청구서인 경우 원본 Folio ID
</ResponseField>

<ResponseField name="createdBy" type="String!">
  청구서 생성자
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  query {
    getAccommodationBills(accommodationId: "01HQKS9V8X2N3P4Q5R6S7T8U9V") {
      id
      billCode
      billName
      totalAmount
      balanceAmount
      status
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "getAccommodationBills": [
        {
          "id": "01HQKS9V8X2N3P4Q5R6S7T8U9X",
          "billCode": "B-ABC123",
          "billName": "객실 요금",
          "totalAmount": 150000,
          "balanceAmount": 0,
          "status": "PAID"
        },
        {
          "id": "01HQKS9V8X2N3P4Q5R6S7T8U9Y",
          "billCode": "B-DEF456",
          "billName": "조식 서비스",
          "totalAmount": 30000,
          "balanceAmount": 30000,
          "status": "PENDING"
        }
      ]
    }
  }
  ```
</CodeGroup>

***

### getBillPayments

특정 청구서에 대한 결제 내역을 조회합니다.

#### GraphQL Signature

```graphql theme={null}
query GetBillPayments($billId: ID!) {
  getBillPayments(billId: $billId) {
    id
    billId
    paymentMethod
    amount
    paidAt
    createdBy
    cancelledAt
    cancelledBy
    virtualAccounts {
      bankCode
      accountNumber
      accountHolder
    }
  }
}
```

#### 파라미터

<ParamField path="billId" type="ID!" required>
  결제 내역을 조회할 청구서 ID
</ParamField>

#### 응답

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

<ResponseField name="billId" type="ID!">
  청구서 ID
</ResponseField>

<ResponseField name="paymentMethod" type="String!">
  결제 수단 (예: `CARD`, `CASH`, `TRANSFER`)
</ResponseField>

<ResponseField name="amount" type="Decimal!">
  결제 금액
</ResponseField>

<ResponseField name="paidAt" type="DateTime!">
  결제 일시
</ResponseField>

<ResponseField name="createdBy" type="String!">
  결제 처리자
</ResponseField>

<ResponseField name="cancelledAt" type="DateTime">
  결제 취소 일시
</ResponseField>

<ResponseField name="cancelledBy" type="String">
  결제 취소자
</ResponseField>

<ResponseField name="virtualAccounts" type="VirtualAccountInfo">
  가상계좌 정보 (결제 수단이 가상계좌인 경우)
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  query {
    getBillPayments(billId: "01HQKS9V8X2N3P4Q5R6S7T8U9X") {
      id
      paymentMethod
      amount
      paidAt
      createdBy
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "getBillPayments": [
        {
          "id": "01HQKS9V8X2N3P4Q5R6S7T8U9Z",
          "paymentMethod": "CARD",
          "amount": 150000,
          "paidAt": "2025-12-23T14:30:00Z",
          "createdBy": "김직원"
        }
      ]
    }
  }
  ```
</CodeGroup>

***

## Mutations

### addBillToFolio

Folio에 새로운 청구서를 추가합니다.

#### GraphQL Signature

```graphql theme={null}
mutation AddBillToFolio($input: AddBillInput!) {
  addBillToFolio(input: $input) {
    id
    billCode
    folioId
    billName
    totalAmount
    balanceAmount
    createdAt
    createdBy
  }
}
```

#### 파라미터

<ParamField path="input.folioId" type="ID!" required>
  청구서를 추가할 Folio ID
</ParamField>

<ParamField path="input.billName" type="String!" required>
  청구서 이름
</ParamField>

<ParamField path="input.totalAmount" type="Decimal!" required>
  총 청구 금액
</ParamField>

<ParamField path="input.balanceAmount" type="Decimal">
  초기 잔액 (기본값: totalAmount)
</ParamField>

<ParamField path="input.reservationId" type="ID">
  연결할 예약 ID (선택사항)
</ParamField>

<ParamField path="input.payments" type="[PaymentInput!]">
  청구서 생성과 동시에 추가할 결제 내역 목록:

  * `paymentMethod`: 결제 수단
  * `amount`: 결제 금액
  * `paidAt`: 결제 일시
  * `virtualAccounts`: 가상계좌 정보 (선택사항)
</ParamField>

<ParamField path="input.settlements" type="[SettlementInput!]">
  동일 Folio 내 다른 청구서와의 정산 정보:

  * `targetBillId`: 정산 대상 청구서 ID
  * `settledAmount`: 정산 금액
</ParamField>

#### 응답

생성된 Bill 객체를 반환합니다.

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    addBillToFolio(
      input: {
        folioId: "01HQKS9V8X2N3P4Q5R6S7T8U9V"
        billName: "스파 이용료"
        totalAmount: 80000
        balanceAmount: 80000
        payments: [
          {
            paymentMethod: "CARD"
            amount: 80000
            paidAt: "2025-12-23T15:00:00Z"
          }
        ]
      }
    ) {
      id
      billCode
      billName
      totalAmount
      balanceAmount
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "addBillToFolio": {
        "id": "01HQKS9V8X2N3P4Q5R6S7T8U9A",
        "billCode": "B-GHI789",
        "billName": "스파 이용료",
        "totalAmount": 80000,
        "balanceAmount": 0
      }
    }
  }
  ```
</CodeGroup>

<Note>
  청구서 생성 시 `payments`를 함께 전달하면 결제가 자동으로 처리되고 `balanceAmount`가 차감됩니다.
</Note>

***

### updateBill

기존 청구서의 정보를 수정합니다.

#### GraphQL Signature

```graphql theme={null}
mutation UpdateBill($input: UpdateBillInput!) {
  updateBill(input: $input) {
    id
    billCode
    billName
    totalAmount
    balanceAmount
    updatedAt
  }
}
```

#### 파라미터

<ParamField path="input.id" type="ID!" required>
  수정할 청구서 ID
</ParamField>

<ParamField path="input.billName" type="String">
  변경할 청구서 이름
</ParamField>

<ParamField path="input.totalAmount" type="Decimal">
  변경할 총 금액
</ParamField>

<ParamField path="input.balanceAmount" type="Decimal">
  변경할 잔액
</ParamField>

#### 응답

수정된 Bill 객체를 반환합니다.

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    updateBill(
      input: {
        id: "01HQKS9V8X2N3P4Q5R6S7T8U9A"
        billName: "스파 이용료 (할인 적용)"
        totalAmount: 70000
        balanceAmount: 0
      }
    ) {
      id
      billCode
      billName
      totalAmount
      balanceAmount
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "updateBill": {
        "id": "01HQKS9V8X2N3P4Q5R6S7T8U9A",
        "billCode": "B-GHI789",
        "billName": "스파 이용료 (할인 적용)",
        "totalAmount": 70000,
        "balanceAmount": 0
      }
    }
  }
  ```
</CodeGroup>

***

### cancelBill

청구서를 취소합니다. 취소된 청구서는 삭제되지 않고 취소 상태로 유지됩니다.

#### GraphQL Signature

```graphql theme={null}
mutation CancelBill($id: ID!, $update: UpdateBillInput) {
  cancelBill(id: $id, update: $update) {
    id
    billCode
    status
    cancelledAt
  }
}
```

#### 파라미터

<ParamField path="id" type="ID!" required>
  취소할 청구서 ID
</ParamField>

<ParamField path="update" type="UpdateBillInput">
  취소 시 추가로 수정할 필드 (선택사항)
</ParamField>

#### 응답

취소된 Bill 객체를 반환합니다.

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    cancelBill(id: "01HQKS9V8X2N3P4Q5R6S7T8U9A") {
      id
      billCode
      status
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "cancelBill": {
        "id": "01HQKS9V8X2N3P4Q5R6S7T8U9A",
        "billCode": "B-GHI789",
        "status": "CANCELLED"
      }
    }
  }
  ```
</CodeGroup>

***

### deleteBill

청구서를 완전히 삭제합니다.

#### GraphQL Signature

```graphql theme={null}
mutation DeleteBill($id: ID!) {
  deleteBill(id: $id)
}
```

#### 파라미터

<ParamField path="id" type="ID!" required>
  삭제할 청구서 ID
</ParamField>

#### 응답

<ResponseField name="success" type="Boolean!">
  삭제 성공 여부 (true 반환)
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    deleteBill(id: "01HQKS9V8X2N3P4Q5R6S7T8U9A")
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "deleteBill": true
    }
  }
  ```
</CodeGroup>

<Warning>
  청구서 삭제 시 연결된 결제(Payment) 정보도 함께 삭제됩니다. 청구서를 보존해야 한다면 `cancelBill`을 사용하세요.
</Warning>

***

### transferBills

청구서를 다른 Folio로 이전하거나 동일 Folio 내에서 다른 예약으로 이동합니다.

#### GraphQL Signature

```graphql theme={null}
mutation TransferBills(
  $inputs: [TransferBillInput!]!
  $folioId: ID!
  $reservationId: ID
) {
  transferBills(
    inputs: $inputs
    folioId: $folioId
    reservationId: $reservationId
  ) {
    id
    billCode
    folioId
    reservationId
    totalAmount
    transferOriginFolioId
  }
}
```

#### 파라미터

<ParamField path="inputs" type="[TransferBillInput!]!" required>
  이전할 청구서 목록:

  * `id`: 이전할 청구서 ID
  * `splitAmount`: 분할 금액 (전체 이전 시 청구서의 totalAmount)
</ParamField>

<ParamField path="folioId" type="ID!" required>
  대상 Folio ID
</ParamField>

<ParamField path="reservationId" type="ID">
  대상 예약 ID (동일 Folio 내 이전 시 필수)
</ParamField>

#### 응답

이전된 Bill 객체 목록을 반환합니다.

#### 동작 방식

1. **동일 Folio 내 이전**: `folioId`가 원본 청구서의 Folio와 동일한 경우, 청구서의 `reservationId`만 변경
2. **다른 Folio로 이전**: 새로운 청구서를 생성하고 `transferOriginFolioId`에 원본 Folio 기록

#### 예제

<CodeGroup>
  ```graphql Request (다른 Folio로 이전) theme={null}
  mutation {
    transferBills(
      inputs: [
        {
          id: "01HQKS9V8X2N3P4Q5R6S7T8U9X"
          splitAmount: 150000
        }
      ]
      folioId: "01HQKS9V8X2N3P4Q5R6S7T8U9W"
    ) {
      id
      billCode
      folioId
      totalAmount
      transferOriginFolioId
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "transferBills": [
        {
          "id": "01HQKS9V8X2N3P4Q5R6S7T8U9B",
          "billCode": "B-JKL012",
          "folioId": "01HQKS9V8X2N3P4Q5R6S7T8U9W",
          "totalAmount": 150000,
          "transferOriginFolioId": "01HQKS9V8X2N3P4Q5R6S7T8U9V"
        }
      ]
    }
  }
  ```
</CodeGroup>

<Note>
  동일 Folio 내 청구서 이전 시 `reservationId`를 필수로 지정해야 합니다.
</Note>

***

### addPaymentToBill

청구서에 결제를 추가합니다.

#### GraphQL Signature

```graphql theme={null}
mutation AddPaymentToBill($input: AddPaymentInput!) {
  addPaymentToBill(input: $input) {
    id
    billId
    paymentMethod
    amount
    paidAt
    createdBy
    virtualAccounts {
      bankCode
      accountNumber
      accountHolder
    }
  }
}
```

#### 파라미터

<ParamField path="input.billId" type="ID!" required>
  결제를 추가할 청구서 ID
</ParamField>

<ParamField path="input.paymentMethod" type="String!" required>
  결제 수단:

  * `CARD`: 카드
  * `CASH`: 현금
  * `TRANSFER`: 계좌이체
  * `VIRTUAL_ACCOUNT`: 가상계좌
  * 기타 커스텀 결제 수단
</ParamField>

<ParamField path="input.amount" type="Decimal!" required>
  결제 금액
</ParamField>

<ParamField path="input.paidAt" type="DateTime!" required>
  결제 일시
</ParamField>

<ParamField path="input.virtualAccounts" type="VirtualAccountInput">
  가상계좌 정보 (결제 수단이 `VIRTUAL_ACCOUNT`인 경우):

  * `bankCode`: 은행 코드
  * `accountNumber`: 계좌번호
  * `accountHolder`: 예금주명
</ParamField>

#### 응답

생성된 Payment 객체를 반환합니다.

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    addPaymentToBill(
      input: {
        billId: "01HQKS9V8X2N3P4Q5R6S7T8U9X"
        paymentMethod: "CARD"
        amount: 150000
        paidAt: "2025-12-23T16:00:00Z"
      }
    ) {
      id
      paymentMethod
      amount
      paidAt
      createdBy
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "addPaymentToBill": {
        "id": "01HQKS9V8X2N3P4Q5R6S7T8U9C",
        "paymentMethod": "CARD",
        "amount": 150000,
        "paidAt": "2025-12-23T16:00:00Z",
        "createdBy": "김직원"
      }
    }
  }
  ```
</CodeGroup>

<Note>
  결제 추가 시 청구서의 `balanceAmount`는 자동으로 차감되지 않습니다. 필요 시 `updateBill`로 수동 조정하세요.
</Note>

***

### updatePayment

기존 결제 정보를 수정합니다.

#### GraphQL Signature

```graphql theme={null}
mutation UpdatePayment($input: UpdatePaymentInput!) {
  updatePayment(input: $input) {
    id
    paymentMethod
    amount
    paidAt
    updatedAt
  }
}
```

#### 파라미터

<ParamField path="input.id" type="ID!" required>
  수정할 결제 ID
</ParamField>

<ParamField path="input.paymentMethod" type="String">
  변경할 결제 수단
</ParamField>

<ParamField path="input.amount" type="Decimal">
  변경할 결제 금액
</ParamField>

<ParamField path="input.paidAt" type="DateTime">
  변경할 결제 일시
</ParamField>

<ParamField path="input.virtualAccounts" type="VirtualAccountInput">
  변경할 가상계좌 정보
</ParamField>

#### 응답

수정된 Payment 객체를 반환합니다.

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    updatePayment(
      input: {
        id: "01HQKS9V8X2N3P4Q5R6S7T8U9C"
        amount: 140000
      }
    ) {
      id
      paymentMethod
      amount
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "updatePayment": {
        "id": "01HQKS9V8X2N3P4Q5R6S7T8U9C",
        "paymentMethod": "CARD",
        "amount": 140000
      }
    }
  }
  ```
</CodeGroup>

***

### cancelPayment

결제를 취소합니다. 취소된 결제는 삭제되지 않고 취소 상태로 유지됩니다.

#### GraphQL Signature

```graphql theme={null}
mutation CancelPayment($id: ID!) {
  cancelPayment(id: $id) {
    id
    paymentMethod
    amount
    cancelledAt
    cancelledBy
  }
}
```

#### 파라미터

<ParamField path="id" type="ID!" required>
  취소할 결제 ID
</ParamField>

#### 응답

취소된 Payment 객체를 반환합니다.

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    cancelPayment(id: "01HQKS9V8X2N3P4Q5R6S7T8U9C") {
      id
      paymentMethod
      amount
      cancelledAt
      cancelledBy
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "cancelPayment": {
        "id": "01HQKS9V8X2N3P4Q5R6S7T8U9C",
        "paymentMethod": "CARD",
        "amount": 150000,
        "cancelledAt": "2025-12-23T17:00:00Z",
        "cancelledBy": "김직원"
      }
    }
  }
  ```
</CodeGroup>

***

### deletePayment

결제를 완전히 삭제합니다.

#### GraphQL Signature

```graphql theme={null}
mutation DeletePayment($id: ID!) {
  deletePayment(id: $id)
}
```

#### 파라미터

<ParamField path="id" type="ID!" required>
  삭제할 결제 ID
</ParamField>

#### 응답

<ResponseField name="success" type="Boolean!">
  삭제 성공 여부 (true 반환)
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    deletePayment(id: "01HQKS9V8X2N3P4Q5R6S7T8U9C")
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "deletePayment": true
    }
  }
  ```
</CodeGroup>

<Warning>
  결제 삭제 시 청구서의 잔액은 자동으로 조정되지 않습니다. 필요 시 `updateBill`로 수동 조정하세요.
</Warning>

***

## 데이터 모델

### Bill

청구서 엔티티입니다.

**주요 필드:**

* `billCode`: 고유 청구서 코드
* `folioId`: 소속 Folio ID
* `reservationId`: 연결된 예약 ID (선택사항)
* `totalAmount`: 총 청구 금액
* `balanceAmount`: 남은 잔액
* `transferOriginFolioId`: 이전된 청구서인 경우 원본 Folio ID
* `originRateSnapshot`: 요금제 스냅샷 참조
* `originInclusionSnapshot`: 포함 항목 스냅샷 참조
* `payments`: 결제 목록
* `settlementsMade`: 이 청구서가 정산한 내역
* `settlementsReceived`: 이 청구서가 받은 정산 내역

### Payment

결제 엔티티입니다.

**주요 필드:**

* `billId`: 소속 청구서 ID
* `paymentMethod`: 결제 수단
* `amount`: 결제 금액
* `paidAt`: 결제 일시
* `cancelledAt`: 결제 취소 일시 (취소된 경우)
* `cancelledBy`: 결제 취소자
* `virtualAccounts`: 가상계좌 정보 (JSON)

### BillSettlement

청구서 간 정산 정보입니다.

**주요 필드:**

* `paymentBillId`: 정산을 수행한 청구서 ID
* `targetBillId`: 정산 대상 청구서 ID
* `settledAmount`: 정산 금액

***

## 에러 처리

<ResponseField name="DATA_NOT_FOUND_ON_ID">
  지정한 ID의 청구서 또는 결제를 찾을 수 없습니다.
</ResponseField>

<ResponseField name="REQUIRED_PARAMETER">
  필수 파라미터가 누락되었습니다.
</ResponseField>

<ResponseField name="INVALID_PARAMETER">
  잘못된 파라미터가 전달되었습니다. 예를 들어:

  * 다른 숙박 시설의 청구서를 이전하려는 경우
  * 다른 Folio의 청구서와 정산하려는 경우
</ResponseField>

***

## 사용 흐름

1. **청구서 생성**: `addBillToFolio`로 Folio에 청구서 추가
2. **결제 추가**: `addPaymentToBill`로 결제 기록
3. **청구서 정산**: 동일 Folio 내 청구서 간 정산 (`settlements` 파라미터)
4. **청구서 이전**: `transferBills`로 다른 Folio로 이전
5. **취소 처리**: `cancelPayment` 또는 `cancelBill`로 취소
6. **삭제**: `deletePayment` 또는 `deleteBill`로 완전 삭제

***

## 관련 API

* [Folio 관리 API](/api-reference/core-svc/folio) - Folio 생성 및 관리
* [예약 관리 API](/api-reference/core-svc/reservation) - 예약과 청구서 연결
* [마일리지 API](/api-reference/core-svc/mileage) - 마일리지 결제 연동
