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

# Twenty로 제품 데이터 가져오기

> 일정에 따라 데이터 웨어하우스의 제품 카탈로그 데이터를 CRM으로 동기화하세요.

이 패턴을 사용하여 데이터 웨어하우스(예: Snowflake, BigQuery, PostgreSQL)의 제품 데이터와 Twenty를 동기화 상태로 유지하세요.

## 워크플로우 구조

1. **트리거**: 일정에 따라
2. **코드**: 데이터 웨어하우스에 질의
3. **코드**(선택): 데이터를 배열로 형식 지정
4. **Iterator**: 각 제품을 순회
5. **레코드 업서트**: Twenty에서 생성 또는 업데이트

<img src="https://mintcdn.com/clara-1b8b12e1/sHXKG8rWt8DAIbvx/images/user-guide/workflows/create_users_workflow.png?fit=max&auto=format&n=sHXKG8rWt8DAIbvx&q=85&s=7af72cfbb3206228a84ea9bb88838b0d" style={{width:'100%'}} width="4550" height="7720" data-path="images/user-guide/workflows/create_users_workflow.png" />

## 단계 1: 트리거 일정 설정

데이터 최신성 요구에 맞는 빈도로 워크플로우가 실행되도록 설정하세요:

* 거의 실시간 동기화를 위해 5분마다
* 중요도가 낮은 데이터의 경우 매시간
* 배치 업데이트의 경우 매일

## 단계 2: 데이터 웨어하우스 질의

최근 데이터를 가져오기 위해 **코드** 액션을 추가합니다:

```javascript theme={null}
export const main = async () => {
  const intervalMinutes = 10; // Match your schedule frequency
  const cutoffTime = new Date(Date.now() - intervalMinutes * 60 * 1000).toISOString();

  // Replace with your actual data warehouse connection
  const response = await fetch("https://your-warehouse-api.com/query", {
    method: "POST",
    headers: {
      "Authorization": "Bearer YOUR_API_KEY",
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      query: `
        SELECT id, name, sku, price, stock_quantity, updated_at
        FROM products
        WHERE updated_at >= '${cutoffTime}'
      `
    })
  });

  const data = await response.json();
  return { products: data.results };
};
```

<Tip>
  `updated_at >= last X minutes`로 필터링하여 최근에 변경된 레코드만 가져옵니다. 동기화를 효율적으로 유지할 수 있습니다.
</Tip>

## 단계 3: 데이터 형식 지정(선택)

웨어하우스가 변환이 필요한 형식으로 데이터를 반환하는 경우, **코드** 액션을 하나 더 추가하세요. 일반적인 변환에는 타입 변환, 필드 이름 변경, 데이터 정리가 포함됩니다.

### 예시: 불리언 및 상태 필드를 포함한 사용자 데이터

```javascript theme={null}
export const main = async (params: {
  users: any;
}): Promise<object> => {
  const { users } = params;
  const usersFormatted = typeof users === "string" ? JSON.parse(users) : users;

  // Convert string "true"/"false" to actual booleans
  const toBool = (v: any) => v === true || v === "true";

  return {
    users: usersFormatted.map((user) => ({
      ...user,
      activityStatus: String(user.activityStatus).toUpperCase(),
      isActiveLast30d: toBool(user.isActiveLast30d),
      isActiveLast7d: toBool(user.isActiveLast7d),
      isActiveLast24h: toBool(user.isActiveLast24h),
      isTwenty: toBool(user.isTwenty),
    })),
  };
};
```

### 예시: 타입 변환이 포함된 제품 데이터

```javascript theme={null}
export const main = async (params: { products: any }) => {
  const products = typeof params.products === "string"
    ? JSON.parse(params.products)
    : params.products;

  return {
    products: products.map(product => ({
      externalId: product.id,
      name: product.name,
      sku: product.sku,
      price: parseFloat(product.price),        // String → Number
      stockQuantity: parseInt(product.stock_quantity),
      isActive: product.status === "active"    // String → Boolean
    }))
  };
};
```

### 예시: 날짜 및 통화 형식 지정

```javascript theme={null}
export const main = async (params: { deals: any }) => {
  const deals = typeof params.deals === "string"
    ? JSON.parse(params.deals)
    : params.deals;

  return {
    deals: deals.map(deal => ({
      ...deal,
      // Convert Unix timestamp to ISO date
      closedAt: deal.closed_timestamp
        ? new Date(deal.closed_timestamp * 1000).toISOString()
        : null,
      // Ensure amount is a number (remove currency symbols)
      amount: parseFloat(String(deal.amount).replace(/[^0-9.-]/g, "")),
      // Normalize stage names
      stage: deal.stage?.toLowerCase().replace(/_/g, " ")
    }))
  };
};
```

### 일반적인 변환

| 소스 형식                | 대상 형식            | 코드                                       |
| -------------------- | ---------------- | ---------------------------------------- |
| `"true"` / `"false"` | `true` / `false` | `v === true \|\| v === "true"`           |
| `"123.45"`           | `123.45`         | `parseFloat(value)`                      |
| `"active"`           | `"ACTIVE"`       | `value.toUpperCase()`                    |
| `1704067200` (Unix)  | ISO 날짜           | `new Date(v * 1000).toISOString()`       |
| `"$1,234.56"`        | `1234.56`        | `parseFloat(v.replace(/[^0-9.-]/g, ""))` |
| `null` / `undefined` | `""`             | `value \|\| ""`                          |

## 단계 4: 제품 순회

**Iterator** 액션을 추가합니다:

* 입력: `{{code.products}}`

배열의 각 제품을 순회합니다.

## 5단계: 각 레코드를 업서트하기

이터레이터 내부에 **레코드 업서트** 액션을 추가합니다:

| 설정        | 값                          |
| --------- | -------------------------- |
| **객체**    | 사용자 지정 제품 객체               |
| **일치 기준** | External ID 또는 SKU(고유 식별자) |
| **Name**  | `{{iterator.item.name}}`   |
| **SKU**   | `{{iterator.item.sku}}`    |
| **Price** | `{{iterator.item.price}}`  |

<Tip>
  생성과 업데이트를 위한 분기 흐름을 따로 만들기보다 **Upsert**(업데이트 또는 생성)를 사용하세요. 구현이 더 빠르고 디버깅이 더 쉽습니다.
</Tip>

## 사용 예시

| 소스            | 데이터             |
| ------------- | --------------- |
| **ERP 시스템**   | 제품 카탈로그, 가격, 재고 |
| **전자상거래 플랫폼** | 주문, 고객, 제품 업데이트 |
| **데이터 웨어하우스** | 집계 지표, 보강된 데이터  |
| **재고 시스템**    | 재고 수준, 재주문 알림   |

## 관련 항목

* [워크플로우 트리거](/l/ko/user-guide/workflows/capabilities/workflow-triggers)
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)
* [Code 액션에서 배열 처리하기](/l/ko/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)
