מיפוי שדות מטופס לסכימה
סכימת 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 ArraytargetClass אומר מאיזו טבלה לשלוף את ערכי ה‑lookup בצעד 3.
City הוא Pointer לטבלת CityList בהתקנות רבות, כלומר objectId ולא שם עיר, ובהתקנות אחרות String חופשי. שלפו סכימה לכל טננט במקום להניח טיפוס.
דרך כלי ה‑MCP
Section titled “דרך כלי ה‑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 “טבלת הטיפוסים של שכבת הטפסים”הטיפוסים כאן אינם הפורמט של 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 (בחירה מרובה) |
מערך של אובייקטי Pointer: [{"__type":"Pointer","className":"Interests","objectId":"uNbbHuMGlK"}] |
מערך של מזהים חשופים נשמר, אבל בכרטיס יוצגו צ’יפים ריקים וקריטריון containedIn בטריגר לא יתאים לו |
File / PrivateFile |
אובייקט File של קובץ שהועלה קודם (ב‑table_* בלבד) |
מעלים קודם את הקובץ — POST /parse/files/<name> (כותרת App ID בלבד, הגוף הוא הקובץ) — ושולחים את אובייקט ה‑File שחזר: {"__type":"File","name":…,"url":…} → 200 ושורה אחת עם הקובץ (נבדק 10.2026). אובייקט File של קובץ שלא הועלה מחזיר 400 {"code":1} אחרי כ‑5 שניות ומשאיר בצד השרת איש קשר ו‑5 שורות יעד כפולות; מחרוזת URL מחזירה 400 (code 111), אבל איש הקשר כבר נוצר. |
מזהה Pointer תקין באורכו אך לא קיים בטבלת ה‑lookup נשמר בשתי הקבוצות, כ‑Pointer שמצביע לשום מקום.
שדות בחירה מרובה
Section titled “שדות בחירה מרובה”array_<purpose>_Pointer_<TargetTable> מאוחסן כמערך של אובייקטי Pointer — כך ה‑CRM עצמו שומר אותו:
// getlead{ "PhoneNumber": "0501234567", "array_interests_Pointer_Interests": [{"__type":"Pointer","className":"Interests","objectId":"uNbbHuMGlK"}, {"__type":"Pointer","className":"Interests","objectId":"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 אמנם מושמטים על ידי שכבת הטפסים עצמה, השדה אינו נכתב והבקשה מחזירה 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וסכימת טבלת היעד - כל שדה שהלקוח ביקש אומת מול הסכימה, וכל חוסר סומן ללקוח
- לכל Pointer נשלפה טבלת ה‑
targetClassונבנתה מפה - כל ערכי המפה מחרוזות באורך 10 תווים בדיוק, לא אובייקט
{"__type":"Pointer"} - תאריכים ISO שטוח מרופד; בוליאנים
true/falseאמיתיים - ערכים ריקים מוסרים מהגוף, ו‑
0/falseלא נמחקים בטעות - המפות מתועדות בקוד עם הערה מאיזו טבלה הן נשלפו ומתי
- getlead · web2table — הדיאלקט של כל נקודת קצה
- אבחון תקלות — “שלחתי שדה והוא לא נשמר”
- מודל הנתונים ומוסכמות — טבלת ההמרה המלאה בין השכבות