מיפוי שדות מטופס לסכימה
אי אפשר לכתוב קוד קליטת טפסים מהזיכרון. סכימת Accounts שונה בין לקוח ללקוח — שדות מותאמים, שדות שהוסרו, שדות שנוספו לפרויקט מסוים — וטבלאות ה‑lookup מחזיקות מזהים שנוצרו במערכת הספציפית הזו.
והמחיר של ניחוש הוא לא שגיאה. הוא הצלחה מדומה:
התהליך — חמישה צעדים, בסדר הזה
Section titled “התהליך — חמישה צעדים, בסדר הזה”| # | הצעד | למה |
|---|---|---|
| 1 | שליפת סכימת Accounts |
לדעת אילו שדות קיימים, ובאיזה טיפוס |
| 2 | שליפת סכימת טבלת היעד (רק ב‑web2table) |
אותו דבר עבור השורה העסקית |
| 3 | שליפת הערכים מכל טבלת lookup שהטופס ממלא | לבנות מפת ערך תצוגה → objectId |
| 4 | אימות כל שדה שהלקוח ביקש מול הסכימה | לסמן חוסרים לפני כתיבת הקוד, לא אחריה |
| 5 | הטמעת המפות בקוד ובנייה | רק עכשיו |
צעדים 1–3 בלתי תלויים זה בזה — אפשר להריץ אותם במקביל.
צעד 1–2 · שליפת הסכימה
Section titled “צעד 1–2 · שליפת הסכימה”דרך ה‑REST API
Section titled “דרך ה‑REST API”curl -sS "https://api.mbapps.co.il/parse/schemas/Accounts" \ -H "X-Parse-Application-Id: $APP_ID" \ -H "X-Parse-Master-Key: $MASTER_KEY" > accounts-schema.jsonimport { writeFile } from 'node:fs/promises';
const res = await fetch("https://api.mbapps.co.il/parse/schemas/Accounts", { headers: { 'X-Parse-Application-Id': APP_ID, 'X-Parse-Master-Key': MASTER_KEY, },});await writeFile('accounts-schema.json', await res.text());import requests
res = requests.get( "https://api.mbapps.co.il/parse/schemas/Accounts", headers={ "X-Parse-Application-Id": APP_ID, "X-Parse-Master-Key": MASTER_KEY, },)with open("accounts-schema.json", "w", encoding="utf-8") as f: f.write(res.text)<?php$ch = curl_init();curl_setopt_array($ch, [ CURLOPT_URL => "https://api.mbapps.co.il/parse/schemas/Accounts", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "X-Parse-Application-Id: {$APP_ID}", "X-Parse-Master-Key: {$MASTER_KEY}", ],]);$response = curl_exec($ch);curl_close($ch);file_put_contents("accounts-schema.json", $response);# accounts_fields.py — שם שדה, טיפוס, ולאיזו טבלה מצביע כל Pointerimport json
fields = json.load(open('accounts-schema.json', encoding='utf-8'))['fields']for name, spec in sorted(fields.items()): print(f"{name:34}{spec['type']:12}{spec.get('targetClass', '')}")פלט טיפוסי (קטע):
City Pointer CityListCompanyId StringEmail StringF_name StringIsAccount BooleanLeadSourceId Pointer LeadSourceLeadStatusId Pointer LeadStatusesName StringPhoneNumber Stringarray_interests_Pointer_Interests Arrayהעמודה השלישית — targetClass — היא מה שאתם צריכים בצעד 3: היא אומרת מאיזו טבלה לשלוף את ערכי ה‑lookup.
שימו לב ל‑City: בהתקנות רבות הוא Pointer לטבלת CityList, כלומר צריך לשלוח objectId ולא שם עיר. בהתקנות אחרות אותו שדה יכול להיות String חופשי. זו בדיוק הסיבה לשלוף סכימה לכל טננט במקום להניח טיפוס.
דרך כלי ה‑MCP
Section titled “דרך כלי ה‑MCP”לסוכן AI או למיישם שעובד מול המערכת דרך שרת ה‑MCP:
// Get-Schema{ "className": "Accounts" }| הפרמטר | טיפוס | חובה | מה הוא עושה |
|---|---|---|---|
className |
string |
לא | שם המחלקה. בלעדיו — מוחזרת הסכימה של כל הטבלאות במערכת; זו תשובה כבדה (עשרות KB), ומספר הטבלאות תלוי‑התקנה. ה‑MCP משמיט את טבלאות המערכת החסומות (Config, _Session, _Role, SMTP ודומיהן) ולכן מחזיר תמיד כמה טבלאות פחות מ‑/parse/schemas |
tablesNameOnly |
boolean |
לא | true מחזיר רק את שמות הטבלאות — שימושי כדי לאתר את השם המדויק של טבלת היעד |
צעד 3 · בניית מפת ה‑Pointer
Section titled “צעד 3 · בניית מפת ה‑Pointer”<select> בטופס שולח טקסט (“פייסבוק”). ה‑API רוצה objectId (“vOZLqOWnQf”). מישהו צריך לתרגם — ומכיוון שאין לטופס דרך מאובטחת לשאול את המערכת בזמן ריצה, התרגום מוטמע בקוד כמפה סטטית.
// Get-Data — שליפת ערכי טבלת ה-lookup{ "table": "LeadSource", "keys": ["Name"], "limit": 200 }| הפרמטר | טיפוס | חובה | הערה |
|---|---|---|---|
table |
string |
✔ | שם הטבלה — כלומר ה‑targetClass מהסכימה |
keys |
string[] |
לא | הקרנת שדות. ["Name"] מספיק לבניית מפה |
limit |
number |
לא | ברירת המחדל היא 5 — כמעט תמיד תרצו לציין ערך. המקסימום: 2000 |
order |
string |
לא | מיון; - בתחילת השם = יורד |
skip |
number |
לא | עימוד |
where |
object |
לא | תנאי סינון — כל מפתח הוא שם שדה, מפתחות מרובים מתחברים ב‑AND |
objectId |
string |
לא | 10 תווים בדיוק — לשליפת רשומה בודדת. אורך אחר נדחה בוולידציה (Too small / Too big) |
מהתוצאה בונים מפה סטטית בקוד:
// נשלף פעם אחת מטבלת LeadSource — עדכנו כשמוסיפים מקור חדש במערכתconst SOURCE_MAP = { 'פייסבוק': 'vOZLqOWnQf', 'גוגל': 'fHG8Rgy8Hr', 'אתר אינטרנט': 'yOTdl4L99Q', 'המלצה': 'Qk2pLm8Zx4',};והטופס משתמש בערך התצוגה, בעוד הקוד שולח את המזהה:
<select name="source"> <option value="">איך הגעתם אלינו?</option> <option>פייסבוק</option> <option>גוגל</option> <option>אתר אינטרנט</option> <option>המלצה</option></select>const data = { LeadSourceId: SOURCE_MAP[f.source] }; // objectId חשוף, או undefinedטבלת הטיפוסים של שכבת הטפסים
Section titled “טבלת הטיפוסים של שכבת הטפסים”הטיפוסים כאן אינם הפורמט ב‑JSON של Parse REST. זו הנקודה שבה נשברות הכי הרבה אינטגרציות, כי snippet של Parse REST נראה נכון ומחזיר 200.
הטבלה הזו מפרידה בין שדות account_* לשדות table_* — כי בשדה Pointer שתי הקבוצות מתנהגות אחרת:
| טיפוס בסכימה | הצורה המומלצת | מה קורה בפועל בצורות אחרות |
|---|---|---|
String |
מחרוזת | מחרוזת ריקה אינה נשמרת — השדה כלל לא נכתב לרשומה. אובייקט ({"a":1}) מפיל את הבקשה ב‑400 code 111 |
Number |
מספר או מחרוזת מספרית — השרת ממיר | 1500 ו‑"1500" → 1500. "1,500" ו‑"abc" → נשמרים כ‑null |
Boolean |
true / false בוליאני |
"true" / "false" כמחרוזת (אותיות קטנות בלבד) מומרים; כל מחרוזת אחרת — "True", "yes", "1" — נשמרת כ‑false. מספר (1 / 0) מפיל את הבקשה ב‑400 code 111 (expected Boolean but got Number). חריג: account_IsAccount מקבל רק בוליאני — מחרוזת "true" מפילה את הבקשה |
Date |
מחרוזת ISO שטוחה ומרופדת: "2026-09-01" |
ISO מלא עובד, וגם {"__type":"Date","iso":"..."} עובד. "2026-9-1" (לא מרופד) מפיל את הבקשה ב‑400 code 111 (expected Date but got String) |
Pointer — account_* |
מזהה חשוף באורך 10 תווים: "vOZLqOWnQf" |
{"__type":"Pointer",…} עובד גם הוא. אורך אחר מפיל את כל הבקשה ב‑400 |
Pointer — table_* |
מזהה חשוף באורך 10 תווים, ורק הוא | אורך אחר וגם {"__type":"Pointer",…} נזרקים בשקט — 200 והשדה חסר. SaleStatusId ב‑Sales נזרק תמיד |
Array (בחירה מרובה) |
מערך של מזהים חשופים: ["uNbbHuMGlK","j17RjXmHdN"] |
מערך אובייקטי Pointer נשמר כפי שנשלח — לא נזרק, אלא פוגם בנתונים בשקט |
File / PrivateFile |
אסור לשלוח | מחזיר 400 — אבל איש הקשר כבר נוצר, ועם אובייקט File נוצרות גם 5 שורות יעד כפולות |
מזהה Pointer תקין באורכו אך לא קיים בטבלת ה‑lookup נשמר בשתי הקבוצות — כ‑Pointer שמצביע לשום מקום. גם את זה צריך לתפוס במפה סטטית מעודכנת.
שדות בחירה מרובה
Section titled “שדות בחירה מרובה”שדה בתבנית array_<purpose>_Pointer_<TargetTable> הוא בחירה מרובה, ומאוחסן כמערך של מזהים חשופים:
// getlead{ "PhoneNumber": "0501234567", "array_interests_Pointer_Interests": ["uNbbHuMGlK", "j17RjXmHdN"] }ב‑web2table מוסיפים את הקידומת של הקבוצה המתאימה:
{ "account_array_interests_Pointer_Interests": ["uNbbHuMGlK", "j17RjXmHdN"], "table_array_tags_Pointer_Tags": ["GGZUYqBjqT", "io3Z6YsMcG"] }בצד הטופס זה בדרך כלל <input type="checkbox"> מרובים או <select multiple>: אוספים את הערכים הנבחרים, מעבירים כל אחד דרך המפה, ושולחים את המערך שהתקבל.
הכלל: שדה שלא מופה — משמיטים, לא מרוקנים
Section titled “הכלל: שדה שלא מופה — משמיטים, לא מרוקנים”זו לא העדפת סגנון. שליחת undefined, null או מחרוזת ריקה במקום ערך חסר מייצרת שתי בעיות: undefined לא עובר סריאליזציה בצורה עקבית בין שפות; ובכל שדה — טקסט, Pointer או Date — שכבת הטפסים אמנם משמיטה בעצמה "" ו‑null (השדה כלל אינו נכתב והבקשה מחזירה 200), אבל אתם בונים על התנהגות לא מתועדת של השרת במקום לשלוט בגוף שאתם שולחים. והגבול דק: ערך לא ריק אבל שגוי בטיפוסו — "2026-9-1" בשדה Date, 1 בשדה Boolean, "abc" במקום מזהה ב‑account_* — כבר מפיל את הבקשה כולה ב‑400.
הניקוי הוא שורה אחת, וכדאי שהיא תהיה השורה האחרונה לפני השליחה:
Object.keys(data).forEach((k) => { if (data[k] == null || data[k] === '') delete data[k]; });data = {k: v for k, v in data.items() if v not in (None, "")}$data = array_filter($data, static fn ($v) => $v !== null && $v !== '');דוגמה מלאה — מסכימה לקוד
Section titled “דוגמה מלאה — מסכימה לקוד”נניח טופס הרשמה לסדנה. הלקוח ביקש: שם פרטי, שם משפחה, טלפון, אימייל, עיר, “איך שמעת עלינו”, הסדנה שנבחרה, ותאריך התחלה מועדף.
אחרי שליפת הסכימות והמפות, מציגים ללקוח את המיפוי — ורק אז כותבים קוד:
שדות הטופס → שדות המערכת (web2table, table = "CourseRegistrations")
שם פרטי → account_F_name String שם משפחה → account_L_name String טלפון → phone String ✔ חובה, בלי קידומת אימייל → email String בלי קידומת עיר → account_City String איך שמעת עלינו → account_LeadSourceId Pointer → LeadSource (מפה: 4 ערכים) הסדנה → table_CourseId Pointer → Courses (מפה: 6 ערכים) תאריך מועדף → table_StartDate Date ISO שטוח
⚠ "מספר משתתפים" שביקשתם לא קיים בסכימת CourseRegistrations. הקרוב ביותר: ParticipantsCount (Number). לאשר לפני שממשיכים.const SOURCE_MAP = { 'פייסבוק': 'vOZLqOWnQf', 'גוגל': 'fHG8Rgy8Hr', 'המלצה': 'Qk2pLm8Zx4' };const COURSE_MAP = { 'קורס יסודות CRM': 'aB3dEf9HiJ', 'סדנת אוטומציות': 'kLm4NoP7Qr' };
function buildBody(f) { const data = { table: 'CourseRegistrations', phone: f.phone, email: f.email,
account_F_name: f.firstName, account_L_name: f.lastName, account_City: f.city, account_LeadSourceId: SOURCE_MAP[f.source], // undefined אם לא נמצא → יימחק table_CourseId: COURSE_MAP[f.course], table_StartDate: f.startDate, // "2026-09-01" };
Object.keys(data).forEach((k) => { if (data[k] == null || data[k] === '') delete data[k]; }); return data;}רשימת בדיקה
Section titled “רשימת בדיקה”- נשלפה סכימת
Accounts - נשלפה סכימת טבלת היעד (
web2table) - כל שדה שהלקוח ביקש אומת מול הסכימה, וכל חוסר סומן ללקוח לפני כתיבת הקוד
- לכל Pointer נשלפה טבלת ה‑
targetClassונבנתה מפה - כל ערכי המפה הם מחרוזות באורך 10 תווים בדיוק — אורך אחר מפיל את הבקשה כולה (
account_*) או נעלם בשקט (table_*) - מצביעים נשלחים כמזהה חשוף, לא כאובייקט
{"__type":"Pointer"}— בשדותtable_*האובייקט נזרק - תאריכים כ‑ISO שטוח מרופד (
"2026-09-01", לא"2026-9-1"— הלא‑מרופד מפיל את הבקשה) - בוליאנים נשלחים כ‑
true/falseאמיתיים, לא כ‑1/0(מפיל את הבקשה);account_IsAccount— אף לא כמחרוזת - ערכים ריקים מוסרים מהגוף, ו‑
0/falseלא נמחקים בטעות - המפות מתועדות בקוד עם הערה מאיזו טבלה הן נשלפו ומתי
- getlead · web2table — הדיאלקט של כל נקודת קצה
- אבחון תקלות — “שלחתי שדה והוא לא נשמר”
- מודל הנתונים ומוסכמות — טבלת ההמרה המלאה בין השכבות