このセクションでは、時間的制約のある在庫の更新を送信する方法を説明します。 エンティティを Google に送信しますリアルタイム更新 API を使用すると、更新を push したり、 エンティティをほぼリアルタイムで把握できます。
この機能は主に、予測できないアップデート、 臨時休業、メニューからの品目の削除、価格の更新といった操作が含まれます Google UI にすぐに反映する必要があります。変更が すぐに反映する必要はありません。その場合は、 バッチ取り込みを使用してください。リアルタイム更新は 5 分以内にしてください
前提条件
リアルタイム更新を実装する前に、次の項目が必要です。
- Maps Booking API が有効になっている場合:
<ph type="x-smartling-placeholder">
- </ph>
- GCP で [API とサービス >ライブラリ
- 「Google Maps Booking API」を検索します。
- サンドボックス インスタンス(「Google Maps Booking API(Dev)」)を見つけて、 有効にする
- 本番環境インスタンス(「Google Maps Booking API」)を見つけて、 有効化
- GCP プロジェクトの編集者のロールを持つサービス アカウントが作成されます。対象 詳細については、以下をご覧ください。 アカウント設定:
- 本番環境またはサンドボックス用のデータフィードがホストされ、取り込まれている。詳しくは バッチ取り込みをご覧ください。
- API 認証のためには、Terraform Registry、 お使いの言語の Google クライアント ライブラリ あります。OAuth 2.0: "https://www.googleapis.com/auth/mapsbooking" を あります。以下のコードサンプルでは、これらのライブラリを使用しています。それ以外の場合は トークン交換を手動で処理する必要があります。詳しくは、 OAuth 2.0 を使用した Google API へのアクセス
概要
リアルタイム更新 API は、2 種類のオペレーションをサポートしています。最初のオペレーションは 既存のエンティティを更新する upsert。「 2 つ目のオペレーションは「削除」です。両方 オペレーションは、リクエスト本文にリストされている一連のエンティティに対して実行されます。マイページ 1 回の API 呼び出しで最大 1,000 個のエンティティを更新できます。API は すべての受信リクエストを処理し、さらに処理するためにキューに入れます。 そのため、RTU リクエストは非同期で処理されます。
リアルタイム更新 API は、サンドボックスと本番環境の 2 つの環境で動作します。 サンドボックス環境は、API リクエストと本番環境のテストに使用されます。 環境を更新して、エンド ツー エンドユーザーの注文ユーザーに表示されるコンテンツを更新します。ホスト名 必要があります。
- サンドボックス -
partnerdev-mapsbooking.googleapis.com
- 本番環境 -
mapsbooking.googleapis.com
エンドポイント
リアルタイム更新 API が受信リクエストを処理するために 2 つのエンドポイントを公開する :
- UPSERT -
/v1alpha/inventory/partners/
PARTNER_ID/feeds/owg.v2/record:batchPush
- 削除 -
/v1alpha/inventory/partners/
PARTNER_ID/feeds/owg.v2/record:batchDelete
パラメータ PARTNER_ID は、アクション センターにあります。 パートナー ID は、[アカウントとユーザー] ページにパートナー ID として表示されます。 以下のスクリーンショットを参照してください。
たとえば、PARTNER_ID の値として 10000001 があるとします。 上のスクリーンショット。サンドボックスで API リクエストを送信するための完全な URL と、 次の例をご覧ください。
# Sandbox UPSERT
https://partnerdev-mapsbooking.googleapis.com/v1alpha/inventory/partners/10000001/feeds/owg.v2/record:batchPush
# Sandbox DELETE
https://partnerdev-mapsbooking.googleapis.com/v1alpha/inventory/partners/10000001/feeds/owg.v2/record:batchDelete
# Production UPSERT
https://mapsbooking.googleapis.com/v1alpha/inventory/partners/10000001/feeds/owg.v2/record:batchPush
# Production DELETE
https://mapsbooking.googleapis.com/v1alpha/inventory/partners/10000001/feeds/owg.v2/record:batchDelete
エンティティの更新
在庫内のエンティティを更新するには、UPSERT エンドポイントを使用します。 HTTP POST リクエストを送信します各 POST リクエストには、 PARTNER_ID パラメータと、 在庫スキーマにリストされている任意のエンティティ タイプの構造化データ。
upsert リクエスト ペイロード
リクエストの本文は、レコードのリストを含む JSON オブジェクトです。各レコード
更新するエンティティに対応します。data_record
フィールドで構成されます。
Base64 でエンコードされたエンティティ ペイロードと generation_timestamp
は、エンティティの更新時刻を示します。
{ "records": [ { "data_record":"BASE_64_ENCODED_ENTITY", "generation_timestamp":"UPDATE_TIMESTAMP" } ] }
上記のペイロードで、次のように置き換えます。
BASE_64_ENCODED_ENTITY: Base64 でエンコードされたオブジェクトの JSON 文字列。 エンティティです。デコードされたエンティティの JSON の構造は、 フィード仕様の例:
{"@type":"MenuSection","name":"My Updated Menu Section","menuId":{"@id":"10824","displayOrder":1},"@id":"853705"}
UPDATE_TIMESTAMP: 送信日時のタイムスタンプを必ず含めてください。 エンティティがバックエンド システムで生成されたものです。このタイムスタンプは 正しい順序に並べ替える必要がありますこのフィールドを指定しない場合は Google がリクエストを受信した時刻に設定されます。更新時
batchPush
リクエストでエンティティを削除すると、generation_timestamp
フィールドが エンティティのバージョニングに使用されます。予測値を確認する リレーショナル インベントリの時間値の形式 説明します。
すべてのリアルタイム更新リクエストは、次の条件を満たしている必要があります。
- ペイロードの本文のサイズは 5 MB を超えないようにしてください。バッチと同様に より多くのデータを適合できるように、空白文字を削除することをおすすめします。
- 1 つの
batchPush
リクエストに含めることができるエンティティは最大 1,000 個です。
例
例 1: レストランの更新
レストランの電話番号を緊急に更新する必要があるとします。お客様の update にはレストラン全体の JSON が含まれます。
次のようなバッチフィードについて考えてみましょう。
{ "@type": "Restaurant", "@id": "restaurant12345", "name": "Some Restaurant", "url": "https://www.provider.com/somerestaurant", "telephone": "+16501234570", "streetAddress": "345 Spear St", "addressLocality": "San Francisco", "addressRegion": "CA", "postalCode": "94105", "addressCountry": "US", "latitude": 37.472842, "longitude": -122.217144 }
HTTP POST によるリアルタイム更新は次のようになります。
JSON
POST v1alpha/inventory/partners/PARTNER_ID/feeds/owg.v2/record:batchPush Host: mapsbooking.googleapis.com Content-Type: application/json { "records": [ { "data_record": { "@type": "Restaurant", "@id": "restaurant12345", "name": "Some Restaurant", "url": "https://www.provider.com/somerestaurant", "telephone": "+16501234570", "streetAddress": "345 Spear St", "addressLocality": "San Francisco", "addressRegion": "CA", "postalCode": "94105", "addressCountry": "US", "latitude": 37.472842, "longitude": -122.217144 } "generation_timestamp": "2022-08-19T17:11:10.750Z" } ] }
Base64
Base64 でエンコードされたペイロードの同じ例。
POST v1alpha/inventory/partners/PARTNER_ID/feeds/owg.v2/record:batchPush Host: mapsbooking.googleapis.com Content-Type: application/json { "records": [ { "data_record": "eyJAdHlwZSI6IlJlc3RhdXJhbnQiLCJAaWQiOiJyZXN0YXVyYW50MTIzNDUiLCJuYW1lIjoiU29tZSBSZXN0YXVyYW50IiwidXJsIjoiaHR0cHM6Ly93d3cucHJvdmlkZXIuY29tL3NvbWVyZXN0YXVyYW50IiwidGVsZXBob25lIjoiKzE2NTAxMjM0NTcwIiwic3RyZWV0QWRkcmVzcyI6IjM0NSBTcGVhciBTdCIsImFkZHJlc3NMb2NhbGl0eSI6IlNhbiBGcmFuY2lzY28iLCJhZGRyZXNzUmVnaW9uIjoiQ0EiLCJwb3N0YWxDb2RlIjoiOTQxMDUiLCJhZGRyZXNzQ291bnRyeSI6IlVTIiwibGF0aXR1ZGUiOjM3LjQ3Mjg0MiwibG9uZ2l0dWRlIjotMTIyLjIxNzE0NH0=" "generation_timestamp": "2022-08-19T17:11:10.750Z" } ] }
例 2: 複数のレストランの更新
1 回の API 呼び出しで 2 つのレストラン エンティティを更新するには、HTTP POST リクエスト 次のようになります。
JSON
POST v1alpha/inventory/partners/PARTNER_ID/feeds/owg.v2/record:batchPush Host: mapsbooking.googleapis.com Content-Type: application/json { "records": [ { "data_record": { "@type": "Restaurant", "@id": "restaurant12345", "name": "Some Restaurant", "url": "https://www.provider.com/somerestaurant", "telephone": "+16501235555", "streetAddress": "345 Spear St", "addressLocality": "San Francisco", "addressRegion": "CA", "postalCode": "94105", "addressCountry": "US", "latitude": 37.472842, "longitude": -122.217144 }, "generation_timestamp": "2022-08-19T17:11:10.850Z" }, { "data_record": { "@type": "Restaurant", "@id": "restaurant123", "name": "Some Other Restaurant", "url": "https://www.provider.com/someotherrestaurant", "telephone": "+16501231235", "streetAddress": "385 Spear St", "addressLocality": "San Mateo", "addressRegion": "CA", "postalCode": "94115", "addressCountry": "US" }, "generation_timestamp": "2022-08-19T17:11:10.850Z" } ] }
Base64
Base64 でエンコードされたペイロードの同じ例。
POST v1alpha/inventory/partners/PARTNER_ID/feeds/owg.v2/record:batchPush Host: mapsbooking.googleapis.com Content-Type: application/json { "records": [ { "data_record": "eyJAdHlwZSI6IlJlc3RhdXJhbnQiLCJAaWQiOiJyZXN0YXVyYW50MTIzNDUiLCJuYW1lIjoiU29tZSBSZXN0YXVyYW50IiwidXJsIjoiaHR0cHM6Ly93d3cucHJvdmlkZXIuY29tL3NvbWVyZXN0YXVyYW50IiwidGVsZXBob25lIjoiKzE2NTAxMjM1NTU1Iiwic3RyZWV0QWRkcmVzcyI6IjM0NSBTcGVhciBTdCIsImFkZHJlc3NMb2NhbGl0eSI6IlNhbiBGcmFuY2lzY28iLCJhZGRyZXNzUmVnaW9uIjoiQ0EiLCJwb3N0YWxDb2RlIjoiOTQxMDUiLCJhZGRyZXNzQ291bnRyeSI6IlVTIiwibGF0aXR1ZGUiOjM3LjQ3Mjg0MiwibG9uZ2l0dWRlIjotMTIyLjIxNzE0NH0=", "generation_timestamp": "2022-08-19T17:11:10.850Z" }, { "data_record": "eyJAdHlwZSI6IlJlc3RhdXJhbnQiLCJAaWQiOiJyZXN0YXVyYW50MTIzIiwibmFtZSI6IlNvbWUgT3RoZXIgUmVzdGF1cmFudCIsInVybCI6Imh0dHBzOi8vd3d3LnByb3ZpZGVyLmNvbS9zb21lcmVzdGF1cmFudCIsInRlbGVwaG9uZSI6IisxNjUwMTIzMTIzNSIsInN0cmVldEFkZHJlc3MiOiIzODUgU3BlYXIgU3QiLCJhZGRyZXNzTG9jYWxpdHkiOiJTYW4gTWF0ZW8iLCJhZGRyZXNzUmVnaW9uIjoiQ0EiLCJwb3N0YWxDb2RlIjoiOTQxMTUiLCJhZGRyZXNzQ291bnRyeSI6IlVTIn0=", "generation_timestamp": "2022-08-19T17:11:10.850Z" } ] }
例 3: メニュー項目の価格を更新する
メニュー品の価格を変更する必要があるとします。
次のようなバッチフィードについて考えてみましょう。
{ "@type": "MenuItemOffer", "@id": "menuitemoffer6680262", "sku": "offer-cola", "menuItemId": "menuitem896532", "price": 2, "priceCurrency": "USD" }
POST によるリアルタイム更新は次のようになります。
JSON
POST v1alpha/inventory/partners/PARTNER_ID/feeds/owg.v2/record:batchPush Host: mapsbooking.googleapis.com Content-Type: application/json { "records": [ { "data_record": { "@type": "MenuItemOffer", "@id": "menuitemoffer6680262", "sku": "offer-cola", "menuItemId": "menuitem896532", "price": 2, "priceCurrency": "USD" }, "generation_timestamp": "2022-08-19T17:20:10Z" } ] }
Base64
Base64 でエンコードされたペイロードの同じ例。
POST v1alpha/inventory/partners/PARTNER_ID/feeds/owg.v2/record:batchPush Host: mapsbooking.googleapis.com Content-Type: application/json { "records": [ { "data_record": "eyJAdHlwZSI6Ik1lbnVJdGVtT2ZmZXIiLCJAaWQiOiJtZW51aXRlbW9mZmVyNjY4MDI2MiIsInNrdSI6Im9mZmVyLWNvbGEiLCJtZW51SXRlbUlkIjoibWVudWl0ZW04OTY1MzIiLCJwcmljZSI6MiwicHJpY2VDdXJyZW5jeSI6IlVTRCJ9", "generation_timestamp": "2022-08-19T17:20:10Z" } ] }
エンティティの追加
データの不整合が生じる可能性があるため、新しいエンティティの追加にリアルタイム更新を使用しないでください。代わりに、バッチフィードを バッチ取り込みの手順をご覧ください。
エンティティの削除
在庫からエンティティを削除するには、DELETE を使用します。 endpoint を使用して HTTP POST リクエストを送信します。各 POST リクエストは、 JSON ペイロードとともに PARTNER_ID パラメータを含める 任意のエンティティ ID が含まれます。
リクエスト ペイロードの削除
削除リクエストの本文は、
更新リクエスト。
また、data_record
フィールドと delete_time
フィールドを含むレコードのリストも含まれています。
{ "records": [ { "data_record":"BASE_64_ENCODED_REFERENCE", "delete_time": "DELETE_TIMESTAMP" } ] }
上記のペイロードで、次のように置き換えます。
BASE_64_ENCODED_REFERENCE: Base64 でエンコードされたオブジェクトの JSON 文字列。 削除対象のエンティティへの参照です。参照は エンティティ タイプと ID から取得できます。たとえば、 MenuSection への参照:
{"@type":"MenuSection","@id":"853705"}
DELETE_TIMESTAMP: 呼び出し時のタイムスタンプを必ず含めてください。 エンティティが削除されたことがわかります。このタイムスタンプは 広告枠に削除を適用する順序を指定します
1 つの batchDelete
リクエストに含めることができるエンティティは最大 1,000 個です。
例
例 1: 2 つの MenuItem
エンティティを削除する
1 回の API 呼び出しで 2 つのメニュー項目を削除するには、HTTP POST リクエストは次のようになります。 次のようになります。
JSON
POST v1alpha/inventory/partners/PARTNER_ID/feeds/owg.v2/record:batchDelete Host: mapsbooking.googleapis.com Content-Type: application/json { "records": [ { "data_record": { "@type": "MenuItem", "@id": "item_1234" }, "delete_time": "2022-08-21T15:23:00.000Z" }, { "data_record": { "@type": "MenuItem", "@id": "item_5678" }, "delete_time": "2022-08-21T15:23:00.000Z" } ] }
Base64
Base64 でエンコードされたペイロードの同じ例。
POST v1alpha/inventory/partners/PARTNER_ID/feeds/owg.v2/record:batchDelete Host: mapsbooking.googleapis.com Content-Type: application/json { "records": [ { "data_record": "eyJAdHlwZSI6Ik1lbnVJdGVtIiwiQGlkIjoiaXRlbV8xMjM0In0=" "delete_time": "2022-08-21T15:23:00.000Z" }, { "data_record": "eyJAdHlwZSI6Ik1lbnVJdGVtIiwiQGlkIjoiaXRlbV81Njc4In0=" "delete_time": "2022-08-21T15:23:00.000Z" }, ] }
例 2: Restaurant
エンティティの削除
バッチフィード内のレストランを削除する状況について考えてみましょう。 削除できるのは、レストラン エンティティのみです。サブエンティティ( 自動的に削除されるため、サービスやメニューを利用できなくなります。
ID を持つレストラン エンティティを削除するサンプル リクエスト
https://www.provider.com/restaurant/12345
:
JSON
POST v1alpha/inventory/partners/PARTNER_ID/feeds/owg.v2/record:batchDelete Host: mapsbooking.googleapis.com Content-Type: application/json { "records": [ { "data_record": { "@type": "Restaurant", "@id": "https://www.provider.com/restaurant/12345" }, "delete_time": "2022-08-19T17:11:10.750Z" } ] }
Base64
Base64 でエンコードされたペイロードの同じ例。
POST v1alpha/inventory/partners/PARTNER_ID/feeds/owg.v2/record:batchDelete Host: mapsbooking.googleapis.com Content-Type: application/json { "records": [ { "data_record": "ewogICJAdHlwZSI6ICJSZXN0YXVyYW50IiwKICAiQGlkIjogImh0dHBzOi8vd3d3LnByb3ZpZGVyLmNvbS9yZXN0YXVyYW50LzEyMzQ1Igp9" "delete_time": "2022-08-19T17:11:10.750Z" } ] }
検証とAPI レスポンス コード
リアルタイム更新 API 呼び出しでは、次の 2 種類の検証が行われます。
リクエスト レベル - この検証では、ペイロードが upsert または delete すべての
data_record
には@id
フィールドと@type
フィールドの両方が含まれています。 これらのチェックは同期され、結果が API に返されます レスポンスの本文ですレスポンス コード 200 と空の JSON 本文{}
は、 検証に合格し、そのリクエスト内のエンティティがキューに追加され、 あります。レスポンス コードが 200 と異なる場合は、これらのうちの 1 つ以上が 検証が失敗し、リクエスト全体(すべての エンティティ)が含まれます。たとえば、data_record
に@type
の場合、次のエラー レスポンスが返されます。{ "error": { "code": 400, "message": "Record:{\"@id\":\"2717/86853/DELIVERY\",\"applicableServiceType\":[\"DELIVERY\",\"TAKEOUT\"],\"menuId\":[{\"@id\":\"2717/DELIVERY\",\"displayOrder\":1},{\"@id\":\"2717/TAKEOUT\",\"displayOrder\":2}],\"name\":\"Salad\",\"offeredById\":[\"2717\"]} has following errors: \nThe entity type could not be extracted from the entity value.\n", "status": "INVALID_ARGUMENT", "details": [ { "@type": "type.googleapis.com/google.rpc.DebugInfo", "detail": "[ORIGINAL ERROR] generic::invalid_argument: Failed to parse one or more rtu records. Record:{\"@id\":\"2717/86853/DELIVERY\",\"applicableServiceType\":[\"DELIVERY\",\"TAKEOUT\"],\"menuId\":[{\"@id\":\"2717/DELIVERY\",\"displayOrder\":1},{\"@id\":\"2717/TAKEOUT\",\"displayOrder\":2}],\"name\":\"Salad\",\"offeredById\":[\"2717\"]} has following errors: \nThe entity type could not be extracted from the entity value.\n [google.rpc.error_details_ext] { message: \"Record:{\\\"@id\\\":\\\"2717/86853/DELIVERY\\\",\\\"applicableServiceType\\\":[\\\"DELIVERY\\\",\\\"TAKEOUT\\\"],\\\"menuId\\\":[{\\\"@id\\\":\\\"2717/DELIVERY\\\",\\\"displayOrder\\\":1},{\\\"@id\\\":\\\"2717/TAKEOUT\\\",\\\"displayOrder\\\":2}],\\\"name\\\":\\\"Salad\\\",\\\"offeredById\\\":[\\\"2717\\\"]} has following errors: \\nThe entity type could not be extracted from the entity value.\\n\" }" } ] } }
エンティティ レベル - ペイロード内の各エンティティは、 リレーショナルスキーマです 検証のこのフェーズで発生した問題は API で報告されない レスポンスが返されます。レポートに掲載されるのは RTU レポート できます。
API 割り当て
リアルタイム API 更新の割り当ては、60 秒ごとに 1,500 リクエスト、つまり 25 リクエスト 平均リクエスト数を割り当てを超過すると、Google から次のエラー メッセージが返されます。
{ "error": { "code": 429, "message": "Insufficient tokens for quota ...", "status": "RESOURCE_EXHAUSTED", "details": [...] } }
この処理を行うには、成功するまで指数関数的に間隔を延ばして呼び出しを再試行します。定期的に割り当てを使い切る場合は、エンティティの追加を検討してください。 1 つの API リクエストで出力できます1 回の API 呼び出しに最大 1,000 個のエンティティを含めることができます。
コードサンプル
以下に、さまざまな環境におけるリアルタイム更新 API の使用方法のサンプルを示します。 対応しています。このサンプルでは、Google Auth ライブラリを使用して、 サービス アカウント キー ファイルが生成されます。 アカウント設定: その他の解決策については、 サーバー間アプリケーションに OAuth 2.0 を使用する クライアント ライブラリの生成にあるスキーマを使用して、インベントリとリアルタイム更新のオブジェクト タイプのソースコードを生成することを検討してください。
エンティティの更新
Node.js
このコードでは、Node.js 用の Google 認証ライブラリを使用します。
/* Sample code for Real-time update batchPush implementation. * * Required libraries: * - google-auth-library */ const {JWT} = require('google-auth-library'); // ACTION REQUIRED: Change this to the path of the service account client secret // file downloaded from the Google Cloud Console. const serviceAccountJson = require('./service-account.json'); // ACTION REQUIRED: Change this to your Partner ID received from Google. // The Partner ID is available on the Partner Portal. const PARTNER_ID = 1234; const HOST = { prod: 'https://mapsbooking.googleapis.com', sandbox: 'https://partnerdev-mapsbooking.googleapis.com' }; // ACTION REQUIRED: Change to 'prod' for production const ENV = 'sandbox'; // Feed name for Order with Google including the version. const FEED_NAME = 'owg.v2'; // Endpoint url const url = `${HOST[ENV]}/v1alpha/inventory/partners/${PARTNER_ID}/feeds/${ FEED_NAME}/record:batchPush`; /** * Send a Real-time update request to update/insert entities */ async function batchUpsert(entities) { /** * Sign JWT token using private key from service account secret file * provided. The client can be created without providing a service account * secret file by implementing Application Default Credentials. * https://github.com/googleapis/google-auth-library-nodejs */ const client = new JWT({ email: serviceAccountJson.client_email, key: serviceAccountJson.private_key, scopes: ['https://www.googleapis.com/auth/mapsbooking'], }); const request = {records: toPushRecords(entities)}; const body = JSON.stringify(request); try { const response = await client.request({ method: 'POST', url, data: body, headers: {'Content-Type': 'application/json'} }); console.log('request body:', body); console.log('response status:', response.status); console.log( 'response data:', response.data); // successful response returns '{}' } catch (error) { console.log('error:', error); } } /** * Maps array of entities to records for batch push requests */ const toPushRecords = (entities) => { return entities.map((entity) => { // Using dateModified to set generation_timestamp. Defaulting to the // current timestamp for records that do not have dateModified. const generation_timestamp = entity.dateModified ? entity.dateModified : new Date().toISOString(); return {data_record: btoa(JSON.stringify(entity)), generation_timestamp}; }); }; // Call batchUpsert with example entities. dateModified is optional and is // used to hold the actual timestamp when the entity was updated/created. batchUpsert([ { '@type': 'MenuItemOffer', '@id': '6680261', 'menuItemId': '18931508', 'price': 15.5, 'priceCurrency': 'USD', 'applicableServiceType': ['DELIVERY', 'TAKEOUT'], 'inventoryLevel': 0, 'dateModified': '2022-06-19T15:43:50.970Z' }, { '@type': 'MenuItemOffer', '@id': '6680262', 'menuItemId': '18931509', 'price': 25.5, 'priceCurrency': 'USD', 'applicableServiceType': ['DELIVERY', 'TAKEOUT'], 'inventoryLevel': 0, 'dateModified': '2022-06-19T15:43:50.970Z' } ]);
Python
このコードでは Python 用の Google 認証ライブラリを使用します。
"""Sample code for the Real-time update batchPush implementation.""" # Required libraries: # - google-auth import base64 import datetime import json from google.auth.transport.requests import AuthorizedSession from google.oauth2 import service_account # ACTION REQUIRED: Change this to the Partner ID received from Google. # Partner ID is available on the Partner Portal. # https://partnerdash.google.com/apps/reservewithgoogle _PARTNER_ID = '1234' # ACTION REQUIRED: Change this to the path of the service account client secret # file downloaded from the Google Cloud Console. _SERVICE_ACCOUNT_KEY_JSON_FILE = 'service-account-creds.json' _HOST_MAP = { 'sandbox': 'https://partnerdev-mapsbooking.googleapis.com', 'prod': 'https://mapsbooking.googleapis.com' } # ACTION REQUIRED: Change to 'prod' for production _ENV = 'sandbox' # Feed name for Order with Google including the version. _FEED_NAME = 'owg.v2' _ENDPOINT = '{}/v1alpha/inventory/partners/{}/feeds/{}/record:batchPush'.format( _HOST_MAP[_ENV], _PARTNER_ID, _FEED_NAME) def batch_upsert(entities): """Makes a batchPush request using the Real-time updates REST service. Args: entities: The list of entity objects to update or add. """ # Creates credentials by providing a json file. Credentials can also be # provided by implementing Application Default Credentials. # https://googleapis.dev/python/google-auth/latest/user-guide.html credentials = service_account.Credentials.from_service_account_file( _SERVICE_ACCOUNT_KEY_JSON_FILE, scopes=['https://www.googleapis.com/auth/mapsbooking']) authorized_session = AuthorizedSession(credentials) # JSON request object batch_request = {'records': [create_push_record(x) for x in entities]} response = authorized_session.post(_ENDPOINT, json=batch_request) print('request body:', json.dumps(batch_request)) print('response status:', response.status_code) print('response data:', response.text) # successful response returns '{}' def create_push_record(entity): """Creates a record from an entity for batchPush requests. Args: entity: The entity object to create the record from. Returns: The constructed record for the batchPush request payload. """ data_bytes = json.dumps(entity).encode('utf-8') base64_bytes = base64.b64encode(data_bytes) # Using dateModified to set generation_timestamp. Defaulting to the # current timestamp for records that do not have dateModified. generation_timestamp = entity.dateModified if 'dateModified' in entity else datetime.datetime.now( ).strftime('%Y-%m-%dT%H:%M:%S.%fZ') return { 'generation_timestamp': generation_timestamp, 'data_record': base64_bytes.decode('utf-8') } # Call batch_upsert with example entities. dateModified is optional and is # used to hold the actual timestamp when the entity was updated/created. batch_upsert([{ '@type': 'MenuItemOffer', '@id': '6680261', 'menuItemId': '18931508', 'price': 15.5, 'priceCurrency': 'USD', 'applicableServiceType': ['DELIVERY', 'TAKEOUT'], 'inventoryLevel': 0, 'dateModified': '2022-06-19T15:43:50.970Z' }, { '@type': 'MenuItemOffer', '@id': '6680262', 'menuItemId': '18931509', 'price': 25.5, 'priceCurrency': 'USD', 'applicableServiceType': ['DELIVERY', 'TAKEOUT'], 'inventoryLevel': 0, 'dateModified': '2022-06-19T15:43:50.970Z' }])
Java
このコードでは Java 用 Google 認証ライブラリを使用します。
パッケージ rtusamples.inventory
および rtusamples.realtime
内のクライアント ソースコード モデルは、クライアント ライブラリを生成するの手順に沿って作成されました。
/* * Required Libraries: * - JDK >= 11 * - google-auth-library-oauth2-http */ package rtusamples; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; import java.io.FileInputStream; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import java.nio.charset.Charset; import java.time.Clock; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import rtusamples.inventory.MenuItemOffer; import rtusamples.inventory.MenuItemOfferType; import rtusamples.inventory.ServiceTypeElement; import rtusamples.realtime.BatchPushGenericRecordRequest; import rtusamples.realtime.GenericRecord; /** Sample code for Real-time update batchPush implementation. */ public final class BasicPush { // ACTION REQUIRED: Change this to your Partner ID received from Google. The Partner ID is // available on the Partner Portal. private static final long PARTNER_ID = 12345678; // ACTION REQUIRED: Change this to the path of the service account client secret file downloaded // from the Google Cloud Console. private static final String JSON_KEY_FULL_PATH = "<path to your JSON credentials>/credentials.json"; // ACTION REQUIRED: Change this to the endpoint that is needed. private static final String ENDPOINT = // "https://partnerdev-mapsbooking.googleapis.com"; // for sandbox "https://mapsbooking.googleapis.com"; // for prod // Feed name for Order with Google including the version. private static final String FEED_NAME = "owg.v2"; private static final ObjectMapper objectMapper = new ObjectMapper(); private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]'Z'"); private static final Charset UTF_8 = Charset.forName("UTF-8"); public static void main(String[] args) throws Exception { /** * Create credentials from service account secret file. Alternatively, the credentials can be * created by implementing Application Default Credentials. * https://github.com/googleapis/google-auth-library-java */ // GoogleCredentials sourceCredentials = // GoogleCredentials.getApplicationDefault() // .createScoped(Arrays.asList("https://www.googleapis.com/auth/mapsbooking")); // ImpersonatedCredentials credentials = // ImpersonatedCredentials.create( // sourceCredentials, // "fo-test@projectname.iam.gserviceaccount.com", // null, // Arrays.asList("https://www.googleapis.com/auth/mapsbooking"), // 300); GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(JSON_KEY_FULL_PATH)) .createScoped(Arrays.asList("https://www.googleapis.com/auth/mapsbooking")); // Create example MenuItemOffer entities, dateModified is optional and is used to hold // the actual timestamp when the entity was updated/created. MenuItemOffer menuItemOfferPizza = new MenuItemOffer(); menuItemOfferPizza.setID("6680261"); menuItemOfferPizza.setType(MenuItemOfferType.MENU_ITEM_OFFER); menuItemOfferPizza.setMenuItemID("18931508"); menuItemOfferPizza.setPrice(15.5); menuItemOfferPizza.setPriceCurrency("USD"); menuItemOfferPizza.setApplicableServiceType( new ServiceTypeElement[] {ServiceTypeElement.TAKEOUT, ServiceTypeElement.DELIVERY}); menuItemOfferPizza.setInventoryLevel(0.0); menuItemOfferPizza.setDateModified("2022-10-07T13:00:00.000Z"); MenuItemOffer menuItemOfferSalad = new MenuItemOffer(); menuItemOfferSalad.setID("6680262"); menuItemOfferSalad.setType(MenuItemOfferType.MENU_ITEM_OFFER); menuItemOfferSalad.setMenuItemID("18931509"); menuItemOfferSalad.setPrice(25.5); menuItemOfferSalad.setPriceCurrency("USD"); menuItemOfferSalad.setApplicableServiceType( new ServiceTypeElement[] {ServiceTypeElement.TAKEOUT, ServiceTypeElement.DELIVERY}); menuItemOfferSalad.setInventoryLevel(0.0); menuItemOfferSalad.setDateModified("2022-10-07T13:00:00.000Z"); // Example array of MenuItemOffer entities to update. List<MenuItemOffer> menuItemOffers = Arrays.asList(menuItemOfferPizza, menuItemOfferSalad); // Create list of GenericRecord from menuItemOffers. List<GenericRecord> menuItemOfferGenericRecords = menuItemOffers.stream() .map( (menuItemOffer) -> toBatchPushRecord(menuItemOffer, menuItemOffer.getDateModified())) .collect(Collectors.toList()); // List of records to be updated/created. List<GenericRecord> recordsToBeUpdated = new ArrayList<>(); // Add list of menuItemOffer generic records. recordsToBeUpdated.addAll(menuItemOfferGenericRecords); // Request object that contains all records. BatchPushGenericRecordRequest batchPushRequest = new BatchPushGenericRecordRequest(); batchPushRequest.setRecords(recordsToBeUpdated.toArray(new GenericRecord[0])); // Execute batchPush request. BasicPush basicPush = new BasicPush(); basicPush.batchPush(batchPushRequest, credentials); } public void batchPush( BatchPushGenericRecordRequest batchPushRequest, GoogleCredentials credentials) throws IOException { credentials.refreshIfExpired(); AccessToken token = credentials.getAccessToken(); String requestBody = objectMapper.writeValueAsString(batchPushRequest); HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri( URI.create( String.format( "%s/v1alpha/inventory/partners/%s/feeds/%s/record:batchPush", ENDPOINT, PARTNER_ID, FEED_NAME))) .header("Content-Type", "application/json") .header("Authorization", String.format("Bearer %s", token.getTokenValue())) .POST(BodyPublishers.ofString(requestBody)) .build(); HttpResponse<String> response = null; try { response = client.send(request, BodyHandlers.ofString()); System.out.println("Request body:" + requestBody); System.out.println("Response status:" + response.statusCode()); System.out.println("Response body:" + response.body()); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } public static <T> GenericRecord toBatchPushRecord(T entity, String dateModified) { GenericRecord genericRecord = new GenericRecord(); try { String json = objectMapper.writeValueAsString(entity); genericRecord.setDataRecord(Base64.getEncoder().encodeToString(json.getBytes(UTF_8))); // Using dateModified to set generation_timestamp. Defaulting to the // current timestamp for records that do not have dateModified. String generationTimestamp = Optional.ofNullable(dateModified) .orElse(OffsetDateTime.now(Clock.systemUTC()).format(TIMESTAMP_FORMATTER)); genericRecord.setGenerationTimestamp(generationTimestamp); } catch (JsonProcessingException e) { System.out.println(e.getMessage()); } return genericRecord; } }
エンティティの削除
Node.js
このコードでは、Node.js 用の Google 認証ライブラリを使用します。
/* Sample code for Real-time update batchDelete implementation. * * Required libraries: * - google-auth-library */ const {JWT} = require('google-auth-library'); // ACTION REQUIRED: Change this to the path of the service account client secret // file downloaded from the Google Cloud Console. const serviceAccountJson = require('./service-account.json'); // ACTION REQUIRED: Change this to your Partner ID received from Google. // The Partner ID is available on the Partner Portal. const PARTNER_ID = 1234; const HOST = { prod: 'https://mapsbooking.googleapis.com', sandbox: 'https://partnerdev-mapsbooking.googleapis.com' }; // ACTION REQUIRED: Change to 'prod' for production const ENV = 'sandbox'; // Feed name for Order with Google including the version. const FEED_NAME = 'owg.v2'; // Endpoint url const url = `${HOST[ENV]}/v1alpha/inventory/partners/${PARTNER_ID}/feeds/${ FEED_NAME}/record:batchDelete`; /** * Send a Real-time update request to delete entities */ async function batchDelete(entities) { try { /** * Sign JWT token using private key from service account secret file * provided. The client can be created without providing a service account * secret file by implementing Application Default Credentials. * https://github.com/googleapis/google-auth-library-nodejs */ const client = new JWT({ email: serviceAccountJson.client_email, key: serviceAccountJson.private_key, scopes: ['https://www.googleapis.com/auth/mapsbooking'], }); const request = { records: toDeleteRecords(entities) }; const body = JSON.stringify(request); try { const response = await client.request({ method: 'POST', url, data: body, headers: {'Content-Type': 'application/json'} }); console.log('request body:', body); console.log('response status:', response.status); console.log('response data:', response.data); // successful response returns '{}' } catch (error) { console.log('error:', error); } } /** * Maps array of entities to records for batch delete requests */ const toDeleteRecords = (entities) => { return entities.map((entity) => { // Using dateModified to set delete_time. Defaulting to the current // timestamp for records that do not have dateModified. const delete_time = entity.dateModified ? entity.dateModified : new Date().toISOString(); return {data_record: btoa(JSON.stringify(entity)), delete_time}; }); }; // Call batchDelete with example entities. dateModified is optional and is // used to hold the actual timestamp when the entity was deleted. batchDelete([ { '@type': 'Menu', '@id': '853706', 'dateModified': '2022-06-19T15:43:50.970Z' }, { '@type': 'Menu', '@id': '853705', 'dateModified': '2022-06-19T15:13:00.280Z' } ]);
Python
このコードでは Python 用の Google 認証ライブラリを使用します。
"""Sample code for the Real-time update batchDelete implementation.""" # Required libraries: # - google-auth import base64 import datetime import json from google.auth.transport.requests import AuthorizedSession from google.oauth2 import service_account # ACTION REQUIRED: Change this to the Partner ID received from Google. # Partner ID is available on the Partner Portal. # https://partnerdash.google.com/apps/reservewithgoogle _PARTNER_ID = '1234' # ACTION REQUIRED: Change this to the path of the service account client secret # file downloaded from the Google Cloud Console. _SERVICE_ACCOUNT_KEY_JSON_FILE = 'service-account-creds.json' _HOST_MAP = { 'sandbox': 'https://partnerdev-mapsbooking.googleapis.com', 'prod': 'https://mapsbooking.googleapis.com' } # ACTION REQUIRED: Change to 'prod' for production _ENV = 'sandbox' # Feed name for Order with Google including the version. _FEED_NAME = 'owg.v2' _ENDPOINT = '{}/v1alpha/inventory/partners/{}/feeds/{}/record:batchDelete'.format( _HOST_MAP[_ENV], _PARTNER_ID, _FEED_NAME) def batch_delete(entities): """Makes a batch delete request using the Real-time updates REST service. Args: entities: The list of entity objects to delete. """ # Creates credentials by providing a json file. Credentials can also be # provided by implementing Application Default Credentials. # https://googleapis.dev/python/google-auth/latest/user-guide.html credentials = service_account.Credentials.from_service_account_file( _SERVICE_ACCOUNT_KEY_JSON_FILE, scopes=['https://www.googleapis.com/auth/mapsbooking']) authorized_session = AuthorizedSession(credentials) # JSON request object batch_request = {'records': [create_delete_record(x) for x in entities]} response = authorized_session.post(_ENDPOINT, json=batch_request) print('request body:', json.dumps(batch_request)) print('response status:', response.status_code) print('response data:', response.text) # successful response returns '{}' def create_delete_record(entity): """Creates a record from an entity for batchDelete requests. Args: entity: The entity object to create the record from. Returns: The constructed record for the batchDelete request payload. """ data_bytes = json.dumps(entity).encode('utf-8') base64_bytes = base64.b64encode(data_bytes) # Using dateModified to set delete_time. Defaulting to the current # timestamp for records that do not have dateModified. delete_time = entity.dateModified if 'dateModified' in entity else datetime.datetime.now( ).strftime('%Y-%m-%dT%H:%M:%S.%fZ') return { 'delete_time': delete_time, 'data_record': base64_bytes.decode('utf-8') } # Call batch_delete with example entities. dateModified is optional and is # used to hold the actual timestamp when the entity was deleted. batch_delete([{ '@type': 'Menu', '@id': '853706', 'dateModified': '2022-06-19T13:10:00.000Z' }, { '@type': 'Menu', '@id': '853705', 'dateModified': '2022-06-19T13:30:10.000Z' }])
Java
このコードでは Java 用 Google 認証ライブラリを使用します。
パッケージ rtusamples.inventory
および rtusamples.realtime
内のクライアント ソースコード モデルは、クライアント ライブラリを生成するの手順に沿って作成されました。
/* * Required Libraries: * - JDK >= 11 * - google-auth-library-oauth2-http */ package rtusamples; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; import java.io.FileInputStream; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import java.nio.charset.Charset; import java.time.Clock; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import rtusamples.inventory.Menu; import rtusamples.inventory.MenuType; import rtusamples.realtime.BatchDeleteGenericRecordsRequest; import rtusamples.realtime.GenericDeleteRecord; /** Sample code for the Real-time update batchDelete implementation. */ public final class BasicDelete { // ACTION REQUIRED: Change this to your Partner ID received from Google. The Partner ID is // available on the Partner Portal. private static final long PARTNER_ID = 123456789; // ACTION REQUIRED: Change this to the path of the service account client secret file downloaded // from the Google Cloud Console. private static final String JSON_KEY_FULL_PATH = "<path to your JSON credentials>/credentials.json"; // ACTION REQUIRED: Change this to the endpoint that is needed. private static final String ENDPOINT = "https://partnerdev-mapsbooking.googleapis.com"; // for sandbox // "https://mapsbooking.googleapis.com" // for prod // Feed name for Order with Google including the version. private static final String FEED_NAME = "owg.v2"; private static final ObjectMapper objectMapper = new ObjectMapper(); private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]'Z'"); private static final Charset UTF_8 = Charset.forName("UTF-8"); public static void main(String[] args) throws Exception { /** * Create credentials from service account secret file. Alternatively, the credentials can be * created by implementing Application Default Credentials. * https://github.com/googleapis/google-auth-library-java */ // GoogleCredentials sourceCredentials = // GoogleCredentials.getApplicationDefault() // .createScoped(Arrays.asList("https://www.googleapis.com/auth/mapsbooking")); // ImpersonatedCredentials credentials = // ImpersonatedCredentials.create( // sourceCredentials, // "fo-test@projectname.iam.gserviceaccount.com", // null, // Arrays.asList("https://www.googleapis.com/auth/mapsbooking"), // 300); GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(JSON_KEY_FULL_PATH)) .createScoped(Arrays.asList("https://www.googleapis.com/auth/mapsbooking")); // Create example Menu entities, dateModified is optional and is used to hold // the actual timestamp when the entity was deleted. Menu menuLunch = new Menu(); menuLunch.setID("853705"); menuLunch.setType(MenuType.MENU); menuLunch.setDateModified("2022-09-19T13:10:00.000Z"); Menu menuDinner = new Menu(); menuDinner.setID("853706"); menuDinner.setType(MenuType.MENU); menuDinner.setDateModified("2022-09-19T13:13:10.000Z"); // Example array of Menu entities to update. List<Menu> menus = Arrays.asList(menuLunch, menuDinner); // Create list of GenericDeleteRecord from menus. List<GenericDeleteRecord> menuGenericDeleteRecords = menus.stream() .map((menu) -> toBatchDeleteRecord(menu, menu.getDateModified())) .collect(Collectors.toList()); // List of records to be deleted. List<GenericDeleteRecord> recordsToBeDeleted = new ArrayList<>(); // Add list of menu generic records. recordsToBeDeleted.addAll(menuGenericDeleteRecords); // Request object that contains all records. BatchDeleteGenericRecordsRequest batchDeleteRequest = new BatchDeleteGenericRecordsRequest(); batchDeleteRequest.setRecords(recordsToBeDeleted.toArray(new GenericDeleteRecord[0])); // Execute batchDelete request. BasicDelete basicDelete = new BasicDelete(); basicDelete.batchDelete(batchDeleteRequest, credentials); } public void batchDelete( BatchDeleteGenericRecordsRequest batchDeleteRequest, GoogleCredentials credentials) throws IOException { credentials.refreshIfExpired(); AccessToken token = credentials.getAccessToken(); String requestBody = objectMapper.writeValueAsString(batchDeleteRequest); HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri( URI.create( String.format( "%s/v1alpha/inventory/partners/%s/feeds/%s/record:batchDelete", ENDPOINT, PARTNER_ID, FEED_NAME))) .header("Content-Type", "application/json") .header("Authorization", String.format("Bearer %s", token.getTokenValue())) .POST(BodyPublishers.ofString(requestBody)) .build(); HttpResponse<String> response = null; try { response = client.send(request, BodyHandlers.ofString()); System.out.println("Request body:" + requestBody); System.out.println("Response status:" + response.statusCode()); System.out.println("Response body:" + response.body()); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } public static <T> GenericDeleteRecord toBatchDeleteRecord(T entity, String dateModified) { GenericDeleteRecord genericRecord = new GenericDeleteRecord(); try { String json = objectMapper.writeValueAsString(entity); genericRecord.setDataRecord(Base64.getEncoder().encodeToString(json.getBytes(UTF_8))); // Using dateModified to set delete_time. Defaulting to the current // timestamp for records that do not have dateModified. String deleteTime = Optional.ofNullable(dateModified) .orElse(OffsetDateTime.now(Clock.systemUTC()).format(TIMESTAMP_FORMATTER)); genericRecord.setDeleteTime(deleteTime); } catch (JsonProcessingException e) { System.out.println(e.getMessage()); } return genericRecord; } }
ユースケース
次のユースケースは、リアルタイム更新、バッチフィード更新、 API 呼び出しの概要は次のとおりです。
シナリオ | 更新するエンティティ | 説明と効果 |
---|---|---|
サービスの無効化 | Service |
予期しない理由でサービスを無効にする必要があります。 リアルタイム更新: 次の場所の 完全なフィード: 完全なフィードからエンティティを更新してください
|
特定の商品が在庫切れの場合 | MenuItemOffer |
リアルタイム更新: カプセル化されている MenuItemOffer を送信する
特定のエンティティに対して inventoryLevel が 0 に設定されている
MenuItem 、その他すべてのデータは変更されていません。 |
メニュー項目の価格変更 | MenuItemOffer |
リアルタイム更新: カプセル化されている MenuItemOffer を送信する
指定された商品の更新価格を price に設定したエンティティ
MenuItem 、その他すべてのデータは変更されていません。 |
新しいトップレベル エンティティを追加
|
Menu 、Restaurant 、Service |
たとえば、レストランに新しいメニューを追加する必要があるとします。 完全なフィード: データフィードにエンティティを追加し、一括取り込みを待ちます。 |
最上位エンティティを完全に削除する
|
Menu 、Restaurant 、Service |
リアルタイム更新: 明示的な削除。 完全なフィード: 完全なフィードからエンティティを削除する前に、 取得されない場合、エンティティは再度追加されます。 |
特定のService に新しい配送地域を追加する |
ServiceArea |
バッチフィード: 該当する ServiceArea エンティティとそのすべてのエンティティを
フィールドはそのままにします。完全なフィードの場合と同様に、新しい配送地域
polygon 、geoRadius 、または postalCode 内で指定されます。 |
配達予定到着時刻(Service )を更新します |
ServiceHours |
バッチフィード: バッチフィードと同じように ServiceHours を送信します。
フィード(leadTimeMin が更新された場合を除く)
必要があります。 |
Service の送料を更新 |
Fee |
バッチフィード: 完全配信の Fee を送信します。
price を更新しました。 |
Service のデリバリーまたはテイクアウトの営業時間を更新 |
ServiceHours |
バッチフィード: バッチフィードと同じように ServiceHours を送信します。
フィード(ただし、opens プロパティと closes プロパティは更新された)
必要があります。 |
Service (最低注文額を変更) |
Fee |
バッチフィード: 次の形式で完全な Fee を送信します:
minPrice
更新 |
MenuItem を完全に削除する |
Menu |
バッチフィード:MenuItem
ありますが、parentMenuSectionId が空です。
|
バッチジョブとリアルタイム更新の処理時間
バッチフィードで更新または削除されたエンティティは、2 日以内に処理されます。 リアルタイム更新によって更新されたエンティティは 5 分。 最新でないエンティティ 14 日後に削除されます
次のいずれかの方法で Google に送信してください。
- 在庫を最新の状態に保つために、1 日あたり複数のバッチジョブ。または
- 1 日に 1 つのバッチジョブとリアルタイム更新により、在庫を最新の状態に保ちます。