בקטע הזה מוסבר איך שולחים ל-Google עדכונים של הפיד שחשובים כרגע. באמצעות Incremental Updates API אפשר לעדכן ולמחוק ישויות בפיד כמעט בזמן אמת.
הפונקציונליות הזו מיועדת בעיקר לעדכונים שלא ניתן לחזות מראש, כמו חסימות חירום. ככלל, כל שינוי שנשלח דרך Incremental Updates API צריך להיות שינוי שצריך להיכנס לתוקף תוך שבוע לכל היותר. אם אין צורך שהשינוי יבוצע באופן מיידי, אפשר להשתמש בעדכון באצווה במקום זאת. המערכת מעבדת עדכונים מצטברים תוך לא יותר מחמש דקות.
הגדרה
כדי להטמיע עדכונים מצטברים:
- כדי ליצור פרויקט, פועלים לפי השלבים המפורטים במאמר יצירה והגדרה של פרויקט.
- כדי ליצור חשבון שירות, פועלים לפי השלבים שמפורטים במאמר הגדרת חשבון שירות. חשוב לזכור שצריך להיות לכם תפקיד 'בעלים' בפרויקט כדי להוסיף תפקיד 'עריכה' לחשבון השירות.
- (אופציונלי, אבל מומלץ) מתקינים את ספריית הלקוח של Google בשפה הרצויה כדי להקל על השימוש ב-OAuth 2.0 בזמן הקריאה ל-API. בדוגמאות הקוד שמפורטות בהמשך נעשה שימוש בספריות האלה. אחרת, תצטרכו לטפל בהחלפות האסימונים באופן ידני, כפי שמתואר במאמר שימוש ב-OAuth 2.0 לגישה ל-Google APIs.
נקודת קצה
כדי להודיע ל-Google על עדכון, שולחים בקשת HTTP POST ל-Incremental Updates API וכוללים מטען נתונים של עדכונים והוספות. הסכימה של מלאי שטחי הפרסום שבה אתם משתמשים קובעת לאיזו נקודת קצה תשלחו את הבקשה:
מלאי בגרסה 2
https://actions.googleapis.com/v2/apps/PROJECT_ID/entities/TYPE/ENTITY_ID:push
מלאי v1
https://actions.googleapis.com/v2/apps/PROJECT_ID/entities/ENTITY_ID:push
כדי להסיר ישות, שולחים בקשת HTTP DELETE לנקודת הקצה הבאה שמתאימה לסכימה של מלאי שטחי הפרסום שבה אתם משתמשים:
מלאי בגרסה 2
https://actions.googleapis.com/v2/apps/PROJECT_ID/entities/TYPE/ENTITY_ID?entity.vertical=FOODORDERING&delete_time=DELETE_TIME
מלאי v1
https://actions.googleapis.com/v2/apps/PROJECT_ID/entities/ENTITY_ID?entity.vertical=FOODORDERING&delete_time=DELETE_TIME
בבקשות שלמעלה, מחליפים את הפרטים הבאים:
- PROJECT_ID: מזהה הפרויקט ב-Google Cloud שמשויך לפרויקט שיצרתם בקטע יצירה והגדרה של פרויקט.
- TYPE (הסכימה של מלאי שטחי הפרסום בגרסה 2 בלבד): סוג הישות (הנכס
@type
) של האובייקט בפיד הנתונים שרוצים לעדכן. - ENTITY_ID: מזהה הישות שכלולה בתוכן המטען. חשוב להקפיד על קידוד של מזהה הישות ב-URL.
- DELETE_TIME (נקודת קצה למחיקה בלבד): שדה אופציונלי לציון המועד שבו הישות נמחקה במערכות שלכם (ברירת המחדל היא מועד קבלת הבקשה). ערך הזמן לא יכול להיות עתידי. כששולחים ישות באמצעות קריאה מצטברת, ניהול גרסאות של ישויות משתמש גם בשדה
delete_time
במקרה של קריאת מחיקה. עיצוב הערך הזה בתורyyyy-mm-ddTHH:mm:ssZ
לדוגמה, יש לכם פרויקט עם המזהה 'delivery-provider-id' שמשתמש בסכימת המלאי v2. אתם רוצים לבצע שינויים במסעדה עם סוג הישות 'MenuSection' ומזהה הישות 'menuSection_122'. נקודת הקצה (endpoint) לעדכונים של הנתונים שלכם תהיה:
https://actions.googleapis.com/v2/apps/delivery-provider-id/entities/MenuSection/menuSection_122:push
כדי להסיר את אותה ישות, צריך לבצע את קריאת ה-API הבאה מסוג HTTP DELETE:
https://actions.googleapis.com/v2/apps/delivery-provider-id/entities/MenuSection/menuSection_122?entity.vertical=FOODORDERING
בקשות ל-Sandbox
בבקשות בסביבת חול, פועלים לפי ההוראות בקטע נקודת קצה שלמעלה, אבל שולחים בקשות אל /v2/sandbox/apps/
במקום אל /v2/apps/
. לדוגמה, מבנה הבקשה למחיקה בארגז החול של סכימה של מלאי v2 הוא:
https://actions.googleapis.com/v2/sandbox/apps/PROJECT_ID/entities/TYPE/ENTITY_ID?entity.vertical=FOODORDERING&delete_time=DELETE_TIME
עדכונים והוספות
פידים יומיים של קבוצות צריכים לכלול גם שינויים שנשלחו דרך ה-API הזה. אחרת, עדכוני האצווה יחליפו את השינויים המצטברים.
מטען ייעודי (payload)
כל בקשת POST חייבת לכלול את פרמטרים הבקשה יחד עם עומס העבודה (payload) של ה-JSON שמכיל את הנתונים המובְנים של כל סוג ישות שמופיע בסכמת המלאי.
קובץ ה-JSON אמור להיראות כמו בפיד האצווה, עם ההבדלים הבאים:
- גודל גוף המטען הייעודי לא יכול לחרוג מ-5MB. בדומה לפידים של קבוצות, מומלץ להסיר רווחים לבנים כדי שיוכלו להיכנס יותר נתונים.
- המעטפה נראית כך:
{ "entity": { "data":"ENTITY_DATA", "vertical":"FOODORDERING" }, "update_time":"UPDATE_TIMESTAMP" }
במטען הייעודי שלמעלה, מחליפים את הפרטים הבאים:
- ENTITY_DATA: ישות בפורמט JSON בסריאליזציה כמחרוזת. יש להעביר את הישות של JSON-LD כמחרוזת בשדה
data
. - UPDATE_TIMESTAMP (אופציונלי): חותמת הזמן שבה הישות עודכנה במערכות שלכם. ערך הזמן לא יכול להיות עתידי. חותמת הזמן שמוגדרת כברירת מחדל היא המועד שבו Google מקבלת את הבקשה. כששולחים ישות באמצעות בקשה מצטברת, ניהול הגרסאות של הישות משתמש גם בשדה
update_time
במקרה של בקשת הוספה/עדכון.
עדכון של ישות
דוגמה 1: עדכון מסעדה
נניח שצריך לעדכן בדחיפות את מספר הטלפון של מסעדה. העדכון מכיל את ה-JSON של המסעדה כולה.
נבחן פיד באצווה שנראה כך:
{ "@type": "Restaurant", "@id": "restaurant12345", "name": "Some Restaurant", "url": "https://www.provider.com/somerestaurant", "telephone": "+16501234567", "streetAddress": "345 Spear St", "addressLocality": "San Francisco", "addressRegion": "CA", "postalCode": "94105", "addressCountry": "US", "latitude": 37.472842, "longitude": -122.217144 }
לאחר מכן, העדכון המצטבר באמצעות HTTP POST יהיה:
POST v2/apps/provider-project/entities/Restaurant/restaurant12345:push Host: actions.googleapis.com Content-Type: application/ld+json { "entity": { "data": { "@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 }, "vertical": "FOODORDERING" } }
דוגמה 2: עדכון המחיר של פריט בתפריט
נניח שצריך לשנות את המחיר של פריט בתפריט. בדומה לדוגמה 1, העדכון צריך לכלול את ה-JSON של הישות ברמה העליונה כולה (התפריט), והפיד משתמש בסכמת המלאי של גרסה 1.
נניח שיש לכם פיד באצווה שנראה כך:
{ "@type": "MenuItemOffer", "@id": "menuitemoffer6680262", "sku": "offer-cola", "menuItemId": "menuitem896532", "price": 3.00, "priceCurrency": "USD" }
לאחר מכן, העדכון המצטבר באמצעות POST יהיה:
POST v2/apps/provider-project/entities/MenuItemOffer/menuitemoffer6680262:push Host: actions.googleapis.com Content-Type: application/ld+json { "entity": { "data": { "@type": "MenuItemOffer", "@id": "menuitemoffer6680262", "sku": "offer-cola", "menuItemId": "menuitem896532", "price": 1.00, "priceCurrency": "USD" }, "vertical": "FOODORDERING" } }
הוספת ישות
כדי להוסיף ישויות, כדאי להימנע משימוש בעדכוני מלאי. במקום זאת, צריך להשתמש בתהליך של פידים בכמות גדולה כפי שמתואר בסכימה של מלאי שטחי הפרסום בגרסה 2.
הסרת ישות
כדי להסיר ישויות ברמה העליונה, משתמשים בנקודת קצה ששונתה מעט, ובבקשה משתמשים ב-HTTP DELETE במקום ב-HTTP POST.
אין להשתמש ב-HTTP DELETE כדי להסיר ישות משנה בתוך ישות ברמה העליונה, כמו 'פריט תפריט' בתוך תפריט. במקום זאת, צריך להתייחס להסרת ישויות משנה כעדכון של ישות ברמה העליונה, שבו הישות המשנית מוסר מהרשימה או מהפרמטר הרלוונטי.
דוגמה 1: מחיקת ישות ברמה העליונה
נניח שאתם רוצים למחוק מסעדה בפיד שמשתמש בסכימת המלאי של גרסה 1. צריך גם למחוק את השירותים והתפריטים שלו.
נקודת קצה לדוגמה של ישות תפריט עם המזהה "https://www.provider.com/restaurant/menu/nr":
DELETE v2/apps/delivery-provider-id/entities/https%3A%2F%2Fwww.provider.com%2Frestaurant%2Fmenu%2Fnr?entity.vertical=FOODORDERING
Host: actions.googleapis.com
נקודת קצה לדוגמה של ישות מסעדה עם המזהה "https://www.provider.com/restaurant/nr":
DELETE v2/apps/delivery-provider-id/entities/https%3A%2F%2Fwww.provider.com%2Frestaurant%2Fnr?entity.vertical=FOODORDERING
Host: actions.googleapis.com
נקודת קצה לדוגמה של ישות שירות עם המזהה "https://www.provider.com/restaurant/service/nr":
DELETE v2/apps/delivery-provider-id/entities/https%3A%2F%2Fwww.provider.com%2Frestaurant%2Fservice%2Fnr?entity.vertical=FOODORDERING
Host: actions.googleapis.com
}
דוגמה 2: הסרת ישויות משנה
כדי להסיר ישות משנה מתוך ישות ברמה העליונה, שולחים את הישות ברמה העליונה עם הישות המשנית שהוסרה מהשדה המתאים. בדוגמה הבאה אנו מניחים שהפיד משתמש בסכימה של מלאי שטחי הפרסום בגרסה 1.
לדוגמה, כדי להסיר אזור שירות, מעדכנים את השירות כך שאזור השירות יוסר מרשימת areaServed
.
POST v2/apps/delivery-provider-id/entities/https%3A%2F%2Fwww.provider.com%2Frestaurant%2Fservice%2Fnr:push
Host: actions.googleapis.com
Content-Type: application/ld+json
{
"entity": {
// Note: "data" is not serialized as a string in our example for readability.
"data": {
"@type": "Service",
"provider": {
"@type": "Restaurant",
"@id": "https://www.provider.com/restaurant/nr"
},
"areaServed": [
{
"@type": "GeoCircle",
"geoMidpoint": {
"@type": "GeoCoordinates",
"latitude": "42.362757",
"longitude": "-71.087109"
},
"geoRadius": "10000"
}
// area2 is removed.
]
...
},
"vertical": "FOODORDERING"
}
}
קודי תגובה של API
קריאה שהצליחה לא מעידה על כך שהפיד תקין או נכון, אלא רק על כך שהקריאה ל-API בוצעה. לשיחות מוצלחות מקבלים קוד תגובה מסוג HTTP 200, יחד עם גוף תגובה ריק:
{}
במקרה של כשל, קוד התגובה של ה-HTTP לא יהיה 200, וגוף התגובה יציין מה השתבש.
לדוגמה, אם המשתמש מגדיר את הערך 'vertical' בתוך המעטפה לערך FAKE_VERTICAL
, תופיע ההודעה הבאה:
{
"error": {
"code": 400,
"message": "Invalid value at 'entity.vertical' (TYPE_ENUM), \"FAKE_VERTICAL\"",
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"field": "entity.vertical",
"description": "Invalid value at 'entity.vertical' (TYPE_ENUM), \"FAKE_VERTICAL\""
}
]
}
]
}
}
דוגמת קוד
בהמשך מפורטות כמה דוגמאות לשימוש ב-Incremental Updates API בשפות שונות. בדוגמאות האלה נעשה שימוש בספריות Google Auth, והן מבוססות על פיד שמשתמש בסכימת המלאי v1. לקבלת פתרונות חלופיים, אפשר לעיין במאמר שימוש ב-OAuth 2.0 לאפליקציות שרת-אל-שרת.
עדכון ישויות
Node.js
הקוד הזה משתמש ב-Google Auth Library ל-Node.js.
const {auth} = require('google-auth-library') const request = require('request'); // The service account client secret file downloaded from the Google Cloud Console const serviceAccountJson = require('./service-account.json') // entity.json is a file that contains the entity data in json format const entity = require('./entity.json') const ENTITY_ID = 'restaurant/http://www.provider.com/somerestaurant' const PROJECT_ID = 'your-project-id' /** * Get the authorization token using a service account. */ async function getAuthToken() { let client = auth.fromJSON(serviceAccountJson) client.scopes = ['https://www.googleapis.com/auth/assistant'] const tokens = await client.authorize() return tokens.access_token; } /** * Send an incremental update to update or add an entity */ async function updateEntity(entityId, entity) { const token = await getAuthToken() request.post({ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, url: `https://actions.googleapis.com/v2/apps/${PROJECT_ID}/entities/${encodeURIComponent(entityId)}:push`, body: { entity: { data: JSON.stringify(entity), vertical: 'FOODORDERING', } }, json: true }, (err, res, body) => { if (err) { return console.log(err); } console.log(`Response: ${JSON.stringify(res)}`) }) } updateEntity(ENTITY_ID, entity)
Python
הקוד הזה משתמש בספריית Google Auth ל-Python.
from google.oauth2 import service_account from google.auth.transport.requests import AuthorizedSession import json import urllib PROJECT_ID = 'your-project-id' ENTITY_ID = 'restaurant/http://www.provider.com/somerestaurant' ENDPOINT = 'https://actions.googleapis.com/v2/apps/%s/entities/%s:push' % ( PROJECT_ID, urllib.quote(ENTITY_ID, '')) # service-account.json is the service account client secret file downloaded from the # Google Cloud Console credentials = service_account.Credentials.from_service_account_file( 'service-account.json') scoped_credentials = credentials.with_scopes( ['https://www.googleapis.com/auth/assistant']) authed_session = AuthorizedSession(scoped_credentials) # Retrieving the entity update_file = open("entity.json") #JSON file containing entity data in json format. data = update_file.read() # Populating the entity with wrapper entity = {} entity['data'] = data #entity JSON-LD serialized as string entity['vertical'] = 'FOODORDERING' request = {} request['entity'] = entity response = authed_session.post(ENDPOINT, json=request) print(response.text) #if successful, will be '{}'
Java
הקוד הזה משתמש ב-Google Auth Library ל-Java.
private static final String PROJECT_ID = "your-project-id"; private static final String ENTITY_ID = "http://www.provider.com/somerestaurant"; /** * Get the authorization token using a service account. */ private static String getAuthToken() { InputStream serviceAccountFile = Example.class.getClassLoader().getResourceAsStream("service-account.json"); ServiceAccountCredentials.Builder credentialsSimpleBuilder = ServiceAccountCredentials.fromStream(serviceAccountFile).toBuilder(); credentialsSimpleBuilder.setScopes(ImmutableList.of("https://www.googleapis.com/auth/assistant")); AccessToken accessToken = credentialsSimpleBuilder.build().refreshAccessToken(); return accessToken.getTokenValue(); } /** * Send an incremental update to update or add an entity. * @param entityId The id of the entity to update. * @param entity the json of the entity to be updated. */ public void updateEntity(String entityId, JSONObject entity) { String authToken = getAuthToken(); String endpoint = String.format( "https://actions.googleapis.com/v2/apps/%s/entities/%s:push", PROJECT_ID, URLEncoder.encode(entityId, "UTF-8")); JSONObject data = new JSONObject(); data.put("data", entity.toString()); data.put("vertical", "FOODORDERING"); JSONObject jsonBody = new JSONObject(); jsonBody.put("entity", data); // Execute POST request executePostRequest(endpoint, authToken, jsonBody); }
הסרת ישויות
Node.js
הקוד הזה משתמש ב-Google Auth Library ל-Node.js.
const {auth} = require('google-auth-library') const request = require('request'); // The service account client secret file downloaded from the Google Cloud Console const serviceAccountJson = require('./service-account.json') // entity.json is a file that contains the entity data in json format const entity = require('./entity.json') const ENTITY_ID = 'restaurant/http://www.provider.com/somerestaurant' const PROJECT_ID = 'your-project-id' /** * Get the authorization token using a service account. */ async function getAuthToken() { let client = auth.fromJSON(serviceAccountJson) client.scopes = ['https://www.googleapis.com/auth/assistant'] const tokens = await client.authorize() return tokens.access_token; } /** * Send an incremental update to delete an entity */ async function deleteEntity(entityId) { const token = await getAuthToken() request.delete({ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, url: `https://actions.googleapis.com/v2/apps/${PROJECT_ID}/entities/${encodeURIComponent(entityId)}?entity.vertical=FOODORDERING`, body: {}, json: true }, (err, res, body) => { if (err) { return console.log(err); } console.log(`Response: ${JSON.stringify(res)}`) }) } deleteEntity(ENTITY_ID)
Python
הקוד הזה משתמש בספריית Google Auth ל-Python.
from google.oauth2 import service_account from google.auth.transport.requests import AuthorizedSession import json import urllib # Service config PROJECT_ID = 'your-project-id' ENTITY_ID = 'restaurant/http://www.provider.com/somerestaurant' DELETE_TIME = '2018-04-07T14:30:00-07:00' ENDPOINT = 'https://actions.googleapis.com/v2/apps/%s/entities/%s?entity.vertical=FOODORDERING&delete_time=%s' % ( PROJECT_ID, urllib.quote(ENTITY_ID, ''), urllib.quote(DELETE_TIME, '')) # service-account.json is the service account client secret file downloaded from the # Google Cloud Console credentials = service_account.Credentials.from_service_account_file( 'service-account.json') scoped_credentials = credentials.with_scopes( ['https://www.googleapis.com/auth/assistant']) authed_session = AuthorizedSession(scoped_credentials) response = authed_session.delete(ENDPOINT) print(response.text) #if successful, will be '{}'
Java
הקוד הזה משתמש ב-Google Auth Library ל-Java.
private static final String PROJECT_ID = "your-project-id"; private static final String ENTITY_ID = "restaurant/http://www.provider.com/somerestaurant"; /** * Get the authorization token using a service account. */ private static String getAuthToken() { InputStream serviceAccountFile = Example.class.getClassLoader().getResourceAsStream("service-account.json"); ServiceAccountCredentials.Builder credentialsSimpleBuilder = ServiceAccountCredentials.fromStream(serviceAccountFile).toBuilder(); credentialsSimpleBuilder.setScopes(ImmutableList.of("https://www.googleapis.com/auth/assistant")); AccessToken accessToken = credentialsSimpleBuilder.build().refreshAccessToken(); return accessToken.getTokenValue(); } /** * Send an incremental update to delete an entity. * @param entityId The id of the entity to delete. */ public void deleteEntity(String entityId) { String authToken = getAuthToken(); String endpoint = String.format( "https://actions.googleapis.com/v2/apps/%s/entities/%s?entity.vertical=FOODORDERING", PROJECT_ID, URLEncoder.encode(entityId, "UTF-8")); // Execute DELETE request System.out.println(executeDeleteRequest(endpoint, authToken)); }
תרחישים לדוגמה
התרחישים הבאים הם דוגמאות לעדכונים מצטברים, לעדכוני פידים מלאים ולתוכן ברמה גבוהה בקריאה ל-API:
תרחיש | ישות ברמה העליונה | תיאור והשפעות |
---|---|---|
השבתת שירות | DisabledService |
אתם צריכים להשבית שירות מסיבה בלתי צפויה. עדכונים מצטברים: שולחים את הישות פידים מלאים: חשוב לעדכן את הישות מהפידים המלאים כך שהערך של |
פריט ספציפי חסר במלאי | Menu |
עדכונים מצטברים: שולחים את הישות Menu שעוטפת את הנתונים, כאשר הערך של offer.inventoryLevel מוגדר ל-0 עבור MenuItem הנתון, וכל שאר הנתונים לא משתנים. |
שינוי במחיר של פריט בתפריט | Menu |
עדכונים מצטברים: שולחים את הישות Menu שעוטפת את הנתונים, כאשר הערך של offer.price מוגדר למחיר המעודכן של MenuItem הנתון, וכל שאר הנתונים לא משתנים. |
הוספת ישות חדשה ברמה העליונה רלוונטי רק לישות מסוגים |
Menu , Restaurant Service |
לדוגמה, אתם צריכים להוסיף תפריט חדש למסעדה. עדכונים מצטברים: שולחים את ישות התפריט החדשה יחד עם ישות המסעדה, והשדה |
מחיקה לתמיד של ישות ברמה העליונה רלוונטי רק לישות מסוגים |
Menu , Restaurant Service |
עדכונים מצטברים: שולחים מחיקה מפורשת. פידים מלאים: חשוב להסיר את הישות מהפידים המלאים לפני האחזור הבא של Google, אחרת הישות תתווסף מחדש. |
הוספת אזור משלוח חדש ב-Service ספציפי |
Service |
פידים מצטברים: שולחים את הישות Service הרלוונטית עם כל השדות שלה, כמו ששולחים בדרך כלל בפידים המלאים, עם אזור חדש להצגה שמצוין ב-areaServed של ה-Service . |
עדכון של שעת ההגעה המשוערת של המשלוח ב-Service |
Service |
פידים מצטברים: שולחים את Service כמו בפיד, מלבד העובדה ש-hoursAvailable.deliveryHours מתעדכן בהתאם. |
עדכון מחירי המשלוח ב-Service |
Service |
פידים מצטברים: שולחים Service מלא עם offers.priceSpecification.price מעודכן. |
עדכון שעות הפעילות של הזמנות במשלוח או לאיסוף עצמי ב-Service |
Service |
פידים מצטברים: שולחים את Service כמו בפיד, מלבד העובדה ש-hoursAvailable מתעדכן בהתאם. |
Service (שינוי סכום ההזמנה המינימלי) |
Service |
פידים מצטברים: שולחים Service מלא עם Service.offers.priceSpecification.eligibleTransactionVolume מעודכן |
מחיקה של MenuItem לתמיד |
Menu |
פידים מצטברים: שולחים את Menu כמו בפיד, אבל MenuItem הזה מוסר מרשימת hasMenuItems . |
יעד זמן עיבוד לטווח ארוך למשימות באצווה ולעדכונים מצטברים
ישות שנוספה באמצעות עדכון באצווה או עדכון מצטבר תעובד תוך יום או יומיים. ישות שתעודכן או תימחק באמצעות קבוצה תעובד תוך שעתיים, ואילו ישות שתעודכן באמצעות עדכון מצטבר תעובד תוך 5 דקות. ישות לא עדכנית נמחקת תוך 7 ימים.
אתם יכולים לשלוח ל-Google:
- כמה משימות באצווה ביום כדי שהמלאי יהיה עדכני, או
- משימה אחת של אצווה ביום ו-APIs מצטברים כדי לשמור על עדכניות המלאי.