פיתוח תוסף סנכרון מוצרים עבור Tec.Delivery
מתודולוגיה ליצירת אינטגרציות עם מערכות WMS, ERP, POS ומערכות הנהלת חשבונות, להלן WMS.
מדריך זה מתאר את הארכיטקטורה המומלצת, תהליך הפיתוח, כללי מיפוי הנתונים ותהליך האינטגרציה עם Merchant Product API לצורך בניית תוספי סנכרון מותאמים אישית עבור קטגוריות, מוצרים, רמות מלאי ותמונות בפלטפורמת Tec.Delivery.
מידע נדרש
לפני תחילת הפיתוח יש לקבל פרטי גישה ומידע טכני הן עבור פלטפורמת Tec.Delivery והן עבור מערכת ה-WMS.
ארכיטקטורת התוסף
מומלץ לחלק את התוסף למודולים עצמאיים האחראים על תזמון, שליפת נתונים, המרת נתונים, העלאת נתונים ל-Tec.Delivery ורישום לוגים.
| מודול | מטרה | דוגמה |
|---|---|---|
| Scheduler | בקרת תדירות ההפעלה | Cron כל 5 דקות |
| Store API Client | שליפת נתונים מהמערכת החיצונית | get_products_store() |
| Tec API Client | שליפת נתונים מ-Tec.Delivery | get_products_tec() |
| Mapper | המרת נתונים | getProducts() |
| Uploader | שליחת נתונים ל-API | put_products() |
| Logger | רישום פעילות התוסף | integration.log |
סוגי סנכרון
| סוג | מטרה | תדירות מומלצת |
|---|---|---|
| סנכרון מלא | בדיקת תקינות כל הקטלוג | 1–2 פעמים ביום |
| סנכרון שינויים | עדכון מוצרים ששונו | כל 15–30 דקות |
| סנכרון מלאי | עדכון יתרות מלאי | כל 5–10 דקות |
סנכרון מלא מתבצע בלילה, שינויים במוצרים מסתנכרנים כל 30 דקות ויתרות מלאי מתעדכנות כל 5 דקות.
עבודה עם Merchant Product API
המתודה put-products של Merchant Product API משמשת להעלאת מוצרים וקטגוריות.
| פרמטר | תיאור | דוגמה |
|---|---|---|
| api | קבוצת API | product |
| method | מתודת API | put-products |
| token | טוקן הסוחר | merchant_token |
| language | שפת נתוני המקור | EN |
| translate | תרגום אוטומטי | true |
| webhook | כתובת URL לקבלת הודעות על סיום עיבוד | https://example.com/webhook |
מיפוי קטגוריות
| שדה במערכת החיצונית | שדה ב-Tec.Delivery | דוגמה |
|---|---|---|
| id | external_id | 1001 |
| parent | external_parent_id | 100 |
| name | name | משקאות |
| modified_at | external_key | 2026-06-01 10:00:00 |
מיפוי מוצרים
| שדה במערכת החיצונית | שדה ב-Tec.Delivery | דוגמה |
|---|---|---|
| id | external_id | SKU123 |
| parent | external_parent_id | 1001 |
| name | name | Coca-Cola 500 ml |
| description | description | משקה מוגז |
| price | price | 199 |
| store_balance | store_balance | 150 |
| modified_at | external_key | 2026-06-01 10:00:00 |
| image_modified_at | external_data | 2026-06-01 10:15:00 |
השדה external_key משמש לזיהוי שינויים במוצר, בעוד שהשדה external_data משמש לזיהוי שינויים בתמונה.
סנכרון תמונות
| פרמטר | תיאור | דוגמה |
|---|---|---|
| url | כתובת תמונה | https://site.com/image.jpg |
| resize | שינוי גודל התמונה | false |
| bgcolor | צבע רקע | #FFFFFF |
| scale | מקדם קנה מידה | 0.8 |
PHP
$tecApiUrl = "https://api.tec.delivery/common/merchant/1.0/";
$limit = 100;
$settings = [
"tec_token" => "YOUR_TEC_TOKEN",
"api_url" => "https://example.com/api/",
"method_get_product" => "products",
"method_get_balance" => "balance",
"webhook" => "https://example.com/webhook",
"auth" => false, // "login:password"
"language" => "IL",
"translate" => true,
];
echo "Start sync\n";
$put = [];
$put = array_merge($put, sync_categories($settings));
$put = array_merge($put, sync_products($settings));
put_products($settings, $put);
echo "Done\n";
function sync_categories(array $settings): array
{
$tecCat = get_products_tec([
"api" => "product",
"method" => "get-categories",
"fields" => [
"product_id",
"name",
"external_id",
"enable",
"external_parent_id",
"external_key"
],
"filter" => [
"enable" => ["eq" => true]
]
], $settings);
foreach ($tecCat as $key => $cat) {
$tecCat[$key]["exist"] = false;
}
$storeCat = get_products_store([
"type" => "category"
], $settings);
$put = [];
foreach ($storeCat as $cat) {
if (empty($cat["id"])) {
continue;
}
$index = findIndexByField($tecCat, "external_id", $cat["id"]);
if ($index !== false) {
$tecCat[$index]["exist"] = true;
if (($tecCat[$index]["external_key"] ?? null) === ($cat["modified_at"] ?? null)) {
continue;
}
}
$put[] = [
"external_id" => $cat["id"],
"external_parent_id" => $cat["parent"] ?? 0,
"name" => $cat["name"],
"external_key" => $cat["modified_at"] ?? null,
"is_category" => true,
"enable" => true,
];
}
foreach ($tecCat as $cat) {
if ($cat["exist"] === false) {
$put[] = [
"product_id" => $cat["product_id"],
"enable" => false,
];
}
}
return $put;
}
function sync_products(array $settings): array
{
$tecProducts = get_products_tec([
"api" => "product",
"method" => "get-all",
"fields" => [
"product_id",
"name",
"external_id",
"external_key",
"external_data",
"enable"
],
"filter" => [
"enable" => ["eq" => true],
"is_category" => ["eq" => false]
]
], $settings);
foreach ($tecProducts as $key => $product) {
$tecProducts[$key]["exist"] = false;
}
$storeProducts = get_products_store([
"type" => "product"
], $settings);
$put = [];
foreach ($storeProducts as $product) {
if (empty($product["id"])) {
continue;
}
$index = findIndexByField($tecProducts, "external_id", $product["id"]);
if ($index === false) {
$put[] = prepare_product($product, true);
continue;
}
$tecProducts[$index]["exist"] = true;
$changed =
($tecProducts[$index]["external_key"] ?? null) !== ($product["modified_at"] ?? null);
$imageChanged =
($tecProducts[$index]["external_data"] ?? null) !== ($product["image_modified_at"] ?? null);
if ($changed || $imageChanged) {
$put[] = prepare_product($product, $imageChanged);
}
}
foreach ($tecProducts as $product) {
if ($product["exist"] === false) {
$put[] = [
"product_id" => $product["product_id"],
"enable" => false,
"store_balance" => 0,
];
}
}
return $put;
}
function prepare_product(array $product, bool $withImage = false): array
{
$item = [
"external_id" => $product["id"],
"external_parent_id" => $product["parent"] ?? 0,
"external_key" => $product["modified_at"] ?? null,
"external_data" => $product["image_modified_at"] ?? null,
"name" => $product["name"] ?? "",
"description" => $product["description"] ?? "",
"price" => isset($product["price"]) ? (float)$product["price"] : 0,
"store_balance" => isset($product["store_balance"]) ? (int)$product["store_balance"] : 0,
"enable" => true,
"is_category" => false,
];
if ($withImage && !empty($product["image_url"])) {
$item["image"] = [
"url" => $product["image_url"],
"resize" => false,
"bgcolor" => "#FFFFFF",
"scale" => 0.8,
"remove_background" => false,
"hash" => [
"field" => "external_data",
"value" => $product["image_modified_at"] ?? ""
]
];
}
return $item;
}
function put_products(array $settings, array $data): void
{
global $tecApiUrl, $limit;
if (empty($data)) {
echo "Nothing to update\n";
return;
}
$chunks = array_chunk($data, $limit);
$total = count($chunks);
foreach ($chunks as $index => $chunk) {
echo "Put products: " . ($index + 1) . " / {$total}\n";
$request = [
"api" => "product",
"method" => "put-products",
"token" => $settings["tec_token"],
"webhook" => $settings["webhook"],
"language" => $settings["language"] ?? "IL",
"translate" => $settings["translate"] ?? true,
"payload" => $chunk
];
get_curl_response($tecApiUrl, $request);
}
}
function get_products_tec(array $request, array $settings): array
{
global $tecApiUrl, $limit;
$offset = 0;
$result = [];
while ($offset !== false) {
echo "Get Tec products offset: {$offset}\n";
$url = $tecApiUrl . "?token={$settings["tec_token"]}&limit={$limit}&offset={$offset}";
$response = get_curl_response($url, $request);
$data = $response["payload"]["data"] ?? [];
$offset = $response["payload"]["offset"] ?? false;
if (!is_array($data)) {
throw new Exception("Invalid Tec API response");
}
$result = array_merge($result, $data);
}
return $result;
}
function get_products_store(array $params, array $settings, string $method = "method_get_product"): array
{
global $limit;
$offset = 0;
$count = 1;
$result = [];
$query = http_build_query($params);
while ($offset < $count) {
echo "Get store products offset: {$offset}\n";
$url = $settings["api_url"]
. $settings[$method]
. "?limit={$limit}&offset={$offset}";
if ($query !== "") {
$url .= "&" . $query;
}
$response = get_curl_response($url, false, $settings["auth"] ?? false);
$count = (int)($response["count"] ?? 0);
$data = $response["data"] ?? [];
if (!is_array($data)) {
throw new Exception("Invalid store API response");
}
$result = array_merge($result, $data);
$offset += $limit;
}
return $result;
}
function get_curl_response(string $url, array|false $raw = false, string|false $basicAuth = false): array
{
$ch = curl_init($url);
$headers = [
"Accept: application/json;charset=utf-8",
"Accept-Encoding: gzip, deflate"
];
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_ENCODING, "");
if ($raw !== false) {
$json = json_encode($raw, JSON_UNESCAPED_UNICODE);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
$headers[] = "Content-Type: application/json";
$headers[] = "Content-Length: " . strlen($json);
}
if ($basicAuth) {
curl_setopt($ch, CURLOPT_USERPWD, $basicAuth);
}
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => $headers,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_CONNECTTIMEOUT => 60,
CURLOPT_TIMEOUT => 60,
]);
$response = curl_exec($ch);
$http = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception("cURL error: {$error}");
}
curl_close($ch);
if ($http < 200 || $http >= 300) {
throw new Exception("HTTP error {$http}: {$response}");
}
$decoded = json_decode($response, true);
if (!is_array($decoded)) {
throw new Exception("Invalid JSON response: {$response}");
}
return $decoded;
}
function findIndexByField(array $array, string $field, mixed $value): int|false
{
foreach ($array as $index => $item) {
if (isset($item[$field]) && $item[$field] == $value) {
return $index;
}
}
return false;
}
אימות הסנכרון
| בדיקה | תוצאה צפויה | דוגמה |
|---|---|---|
| יצירת קטגוריה | הקטגוריה מופיעה בקטלוג | קטגוריה חדשה יובאה |
| יצירת מוצר | המוצר מופיע בקטלוג | SKU חדש יובא |
| שינוי מוצר | השינויים הוחלו בהצלחה | המחיר עודכן |
| שינוי מלאי | המלאי עודכן | 150 → 75 |
| מחיקת מוצר | המוצר הושבת | enable = false |
המלצות לפיתוח
רשימת בדיקה סופית
- טוקן הסוחר התקבל.
- מימוש שליפת קטגוריות הושלם.
- מימוש שליפת מוצרים הושלם.
- מימוש שליפת מלאי הושלם.
- סנכרון תמונות הוגדר.
- סנכרון מצטבר מומש.
- רישום שגיאות הוגדר.
- מנגנון ניסיונות חוזרים הוגדר.
- Webhook לעיבוד תוצאות הוגדר.
- הבדיקות הושלמו באמצעות נתוני ייצור.