דלגו לתוכן

או התחילו מנקודה בטוחה: אינדקס הכלים המלא · מפת הטבלאות המרכזיות

מערכת חדשה — 14 יום חינם

סקילים — נתונים ואינטגרציות

עודכן 10.10.2026

שלושה סקילים למה שנכנס ומה שיוצא.

הסקיל הכיוון
myb-p-data-import קובץ ← CRM
myb-p-public-forms אתר / דף ציבורי ← CRM
myb-p-create-update-reports CRM ← שאלה

טעינה המונית מאקסל/CSV וייצור נתוני דמה: זיהוי לקוחות קיימים (מייל, טלפון, שם או objectId), מיפוי כותרות בעברית לשדות, פתרון ערכי lookup, הסרת כפילויות ויצירה מרוכזת.

1. קריאת הקובץ → גיליונות, כותרות, מספר שורות
2. סיווג העמודות → מזהה / שדה CRM / מטא-דאטה
3. פתרון lookups → objectIds למשתמשים, סטטוסים, מקורות
4. התאמת רשומות → מציאת או יצירת Accounts
5. הסרת כפילויות → מהמקור, לפני הכתיבה
6. יצירה → Create-Many
7. דיווח → נוצרו / דולגו / נכשלו

הטבלה היא דוגמת מיפוי מלקוח ספציפי; שמות השדות אינם שמות מערכת. במערכת חדשה (vanilla) קיימים Accounts.LeadSourceId (← LeadSource), Accounts.LeadOwnerId, Sales.Source (String) ו‑Sales.SaleStatusId, ואילו SaleSource/SaleSourceList, C_LeadSource/C_LeadSourceList ו‑NewCoordinator אינם קיימים בה (כפי שרואים ב‑Get-Schema). בנו מיפוי מול הסכימה החיה.

הכותרת בקובץ הטבלה השדה התפקיד
מייל / אימייל Accounts Email מזהה (חיפוש)
טלפון / מספר טלפון Accounts PhoneNumber מזהה (חיפוש)
AccountId.objectId Accounts objectId מזהה ישיר
שם Accounts Name, F_name, L_name שדה ישיר
כותרת Sales Name שדה ישיר
סטטוס / סטטוס מכירה Sales SaleStatusId lookup ← SaleStatuses
מקור המכירה Sales SaleSource lookup ← SaleSourceList
מקור הליד Accounts C_LeadSource lookup ← C_LeadSourceList
אחראי מכירה / יועץ Sales OwnerId lookup ← _User
מתאם Accounts LeadOwnerId, NewCoordinator lookup ← _User
תאריך התקשרות הבא Sales NextStepDate המרת תאריך
המזהה בקובץ האסטרטגיה האמינות
AccountId / objectId חיפוש ישיר עם $in מושלמת
מייל חיפוש ב‑Accounts.Email גבוהה
טלפון נרמול ← חיפוש regex בינונית-גבוהה
שם מוצא אחרון — חיפוש לפי L_name נמוכה — הרבה התאמות שווא
אין יצירת Accounts חדש —

ליד חדש נוצר כרשומת Accounts עם IsAccount: false ושדות ליד (LeadStatusId, C_LeadSource, LeadOwnerId, NewCoordinator).

JSON
// Pointer
{ "__type": "Pointer", "className": "SaleStatuses", "objectId": "abc1234567" }
// Date
{ "__type": "Date", "iso": "2026-01-01T00:00:00.000Z" }

גודל אצווה: עד ~50 רשומות בקריאה אחת; לסטים גדולים פיצול ל‑46–50.

יצירה דו‑שלבית (לידים + מכירות): Create-Many על Accounts ← objectIds ← מיפוי כל objectId לנתוני המכירה ← Create-Many על Sales. התשובה של Create-Many מחזירה objectIds באותו סדר של מערך הקלט; מיפוי לפי מיקום.

  1. $in נקטע בשקט. ערך אחד שמחזיר הרבה תוצאות “בולע” את המכסה ומפיל בשקט ערכים אחרים. ודאו שמספר הרשומות שנמצאו תואם לציפייה.
  2. נרמול טלפונים. מספרים ישראליים מגיעים בעשרות פורמטים; רדו לגרעין של 9 ספרות.
  3. חיפוש לפי שם משפחה — “כהן” יתאים להמון לקוחות לא קשורים.
  4. מגבלה ידועה — Get-all-Users מחזיר רק 100 משתמשים ראשונים. משתמש שאינו שם: אל תעקפו את שכבת ההרשאות; סמנו את השורות כחסומות והסלימו.
  5. Get-Data מחזיר 5 שורות כברירת מחדל. תמיד limit מפורש.

הדוח הופך את הספירות שממילא מריצים למסמך אמון חתום, בחמישה חלקים: טבלת ספירות (שורות במקור ← יובאו ← דולגו, עם סיווג סיבה ← אימות חי ב‑Count-Data) · סיכום מיפוי בשפה עסקית · רשימת חריגים עם הכרעה לכל קבוצה · פרוטוקול דגימה שבו הלקוח בוחר 5–10 רשומות שהוא מכיר ומשווים אותן יחד מול המקור · בלוק חתימה עם “הנתונים נבדקו ואושרו להפעלה”.

נכשלה דגימה? מתקנים ← מאמתים מחדש ← מריצים את הדגימה שוב על הפריטים שנכשלו. לעולם לא אוספים חתימה על נתונים שידוע שהם שגויים.

נתוני דמה — שיחות MyChat / WhatsApp

Section titled “נתוני דמה — שיחות MyChat / WhatsApp”

מדריך עצמאי בסקיל: references/mychat-conversations.md. העיקר:

שלוש שכבות, מלמטה למעלה: Channels (מספר עסקי) ← Conversations (שרשור לכל טלפון לקוח) ← ConversationMessages (Create-Many). שדה סוג הערוץ הוא TypeId, לא ChannelTypeId.

כללי הודעה: Direction הוא מילולית "Incomeing"/"Outgoing" — שומרים על שגיאת הכתיב. Identity הוא טלפון הלקוח בפורמט בינלאומי בלי +. Message (עם escaping ל‑HTML) חייב להתאים ל‑Data.text.body. כל Data.id (wamid) ייחודי. חלון 24 השעות נאכף: טקסט חופשי יוצא יותר מ‑24 שעות אחרי ההודעה הנכנסת האחרונה חייב להיות template, אחרת failed עם קוד 131047.

מגבלות תצוגה נוספות: תיבת MyChat מציגה רק שיחות על ערוץ מחובר אמיתי, וערוץ סינתטי לא יופיע שם; חשפו את ההיסטוריה על כרטיס הלקוח דרך ווידג’ט Conversations מסונן לפי AccountId. createdAt לא ניתן לתיארוך לאחור; הכרונולוגיה נבנית מ‑Data.timestamp/sentAt/LastMessageAt.


כל טופס שגולש בלי התחברות שולח למערכת, ובחירת המנגנון הנכון. הסקיל נפתח במדריך החלטה ומחזיק תיקייה לכל שיטה. החליף ב‑1.5.0 את myb-p-web2lead-web2table ואת myb-p-csat-survey.

התיקייההמנגנוןמתי
native-form/Database form נייטיבי בדף ציבורי של אתר ה‑CRM, מול טבלת קליטה שפתוחה ליצירה בלבד; reCAPTCHA של הפלטפורמהטופס שאתם מארחים: סקר, משוב, רישום, בקשה — בלי טלפון חובה ובלי קוד שמירה
capture-data/אותו טופס במצב data-use-caputure-data → הפונקציה simbla-capture-dataהטבלה חייבת להישאר סגורה, או שצריך זיהוי כפילויות, שדות חובה וברירות מחדל בשרת — משורת Config אחת
web2/קוד ל‑getlead / web2table ב‑JavaScript, PHP, Python, C# ו‑Node.js, מול הסכימה האמיתיתטופס שיושב מחוץ ל‑CRM — אתר הלקוח, וורדפרס, Wix, מערכת אחרת
csat/סקר שביעות רצון בסגירת פנייה: לחיצה אחת מהמייל, השלכה על הפנייה, משימה על דירוג שלילי, דוחותוריאנט נייטיבי בלי נתון אישי בקישור, או וריאנט web2table. המתכון המלא

כלל האצבע: אנחנו מארחים את הטופס ← טופס נייטיבי. מישהו אחר מארח ← web2. הטבלה חייבת להישאר סגורה או צריך זיהוי כפילויות ← capture-data. כל המנגנונים נבדקו מקצה לקצה על מערכת חיה (10.2026), כולל בדיקות שליליות: קריאה אנונימית בלי טוקן נדחית, עדכון אנונימי נדחה כל עוד ה‑CLP סגור.

טריגרים: “טופס פומבי”, “סקר”, “משוב אחרי אירוע”, “טופס רישום בלי התחברות”, “חיבור טופס לאתר”, “דף נחיתה”, “סקר שביעות רצון”, “CSAT”. הסעיפים הבאים מפרטים את מסלול web2 — ההבדלים מול שני המסלולים האחרים בעמודי הטפסים.

אי אפשר לייצר קוד נכון מהזיכרון. סכימת Accounts וסכימת טבלת היעד שונות בין לקוח ללקוח (שדות מותאמים, תוויות ששונו, שדות שהוסרו), ושדות Pointer דורשים objectIds חיים. המחיר של ניחוש הוא כישלון שקט: 200 מה‑API, ורשומה שנוחתת שגוי.

1. Get-Schema על Accounts → שמות וטיפוסים זמינים
2. Get-Schema על טבלת היעד (web2table) → אותו דבר
3. Get-Data על כל targetClass של Pointer מבוקש → מפת ערך→objectId
4. Get-Data על LeadStatuses (סטטוס מותאם) → אימות ה-objectId
5. אימות כל שדה שהמשתמש ביקש מול הסכימה → שדה שלא קיים — מסמנים, לא משמיטים בשקט
6. רק עכשיו — ייצור קוד
  1. איזו שפה / framework?
  2. איפה הקוד רץ — דף נחיתה ב‑WordPress/Wix/Elementor (דפדפן), או נקודת קצה בשרת (Express, Flask, Laravel, ASP.NET) שהטופס מגיש אליה קודם?
  3. מה קורה בהצלחה? — הפניה לדף תודה? אישור בתוך הדף? אירוע GTM? איפוס הטופס?
  4. יש captcha או שדות אנטי‑בוט? — reCAPTCHA, Turnstile, honeypot. הטוקנים שלהם מאומתים לפני ההעברה הלאה.
  5. הטופס בדומיין אחר מ‑api.mbapps.co.il? — כמעט תמיד. Parse Cloud Functions מאפשרות CORS לנקודות הקצה האלה כברירת מחדל.

טיפוסים — שכבת הטפסים אינה Parse REST

Section titled “טיפוסים — שכבת הטפסים אינה Parse REST”
טיפוס בסכימה מה שולחים הערה
String מחרוזת כמו שהוא
Number מספר או מספר כמחרוזת ה‑API ממיר
Boolean בוליאני אמיתי true/false גם "true"/"false" עובדות ברוב השדות — אבל account_IsAccount מקבל רק בוליאני אמיתי (מחרוזת → 400). המספר 1 → 400
Date מחרוזת ISO מרופדת (2026-06-01) או ISO מלא גם {__type:"Date"} נקלט; מה שנדחה הוא תאריך לא מרופד (2026-6-1 → 400)
Pointer מחרוזת ה‑objectId בת 10 התווים בשדות table_* אובייקט {__type:"Pointer"} או מזהה באורך שגוי נזרקים בשקט (200, שדה ריק); בשדות account_* האובייקט נקלט ואורך שגוי → 400
Array מערך של אובייקטי Pointer מערך של אובייקטי Pointer — {"__type":"Pointer","className":…,"objectId":…} — הפורמט שה‑CRM עצמו שומר בשדה array_*_Pointer_*. מערך של מזהים חשופים נשמר גם הוא, אבל בכרטיס יוצגו צ’יפים ריקים וקריטריון containedIn בטריגר לא יתאים לו (נבדק ב‑web2table, 10.2026)
File ב‑web2table: אובייקט File של קובץ שהועלה קודם מעלים קודם את הקובץ — POST /parse/files/<name> (כותרת App ID בלבד, הגוף הוא הקובץ) — ושולחים את אובייקט ה‑File שחזר: {"__type":"File","name":…,"url":…} → 200 ושורה אחת עם הקובץ (נבדק 10.2026). אובייקט File של קובץ שלא הועלה מחזיר 400 {"code":1} אחרי כ‑5 שניות ומשאיר בצד השרת איש קשר ו‑5 שורות יעד כפולות; מחרוזת URL מחזירה 400 (code 111), אבל איש הקשר כבר נוצר.

לכל נקודת קצה שורה משלה בטבלת Config: AcceptWebLeads ל‑getlead, AcceptWebToTable ל‑web2table, AcceptWebToCaseLeads ל‑web2case. שורה חסרה או false ← 400 עם "AcceptWebToTable is Disabled" (לא 403), לפני שהבקשה מגיעה לזיהוי כפילויות.

השדה הערך
Name "AcceptWebToTable" (String)
Value { "AcceptWebToTable": true } (Object — המפתח חוזר בתוך האובייקט)

הפונקציה קוראת את המפתח הפנימי; ה‑Name החיצוני הוא מפתח החיפוש. בוליאני חשוף true לא יעבוד.

שורות כפולות לאותו דגל גורמות ל‑400 בלתי צפוי: הקוד לוקח query.first(), ובפועל השורה שנוצרה ראשונה מנצחת תמיד. שמרו שורה אחת לכל דגל. פירוט: דגלי Config.

  1. מהסכימה — ה‑targetClass של ה‑Pointer (למשל LeadSource עבור LeadSourceId).
  2. Get-Data(table: "<targetClass>", keys: ["Name"], limit: 200) — שליפת האפשרויות.
  3. ייצור מפה סטטית name → objectId בקוד, עם הערה איך להרחיב.
  4. ה‑<select> מגיש את ה‑Name, והקוד מתרגם לפני השליחה.

טבלת lookup גדולה או משתנה תדיר? שליפה בזמן ההגשה (קריאת REST נוספת אחת). ברירת המחדל היא מפה סטטית.


שני סוגי תצוגות, שניהם ב‑_DynamicQueries ושניהם ב‑Create-or-Update-Report:

דוחות שאילתות
איפה apps/mybusiness/reports דף ישות (apps/mybusiness/sales)
מה זה תצוגה אנליטית עצמאית פילטר שמור בתפריט הנפתח של אותו דף
מזוהה לפי PageName/PageId + FormName בפורמט dynamic-table-formP<number>
הסוג המאפיין מתי
רגיל IsAggr: false רשימת רשומות עם פילטרים, ייצוא
מצטבר IsAggr: true “כמה” ו“סך הכל” — לפי חודש, לפי אחראי
פיבוט IsAggr: true + PivotInfo פילוח דו‑ממדי (אחראי × חודש)
עם שדות מחושבים עמודות נגזרות מרווח רווח, אחוזים
דוח מורכב רק SubqueriesInfo כמה תת‑דוחות בתצוגה אחת

שלישיית השדות — קריטית לדוחות מצטברים

Section titled “שלישיית השדות — קריטית לדוחות מצטברים”

שלושה ערכים חייבים להתיישר לכל עמודה. אחד שגוי — והדוח נכשל בשקט או זורק Group object must contain values:

התכונה הפורמט דוגמה ל‑AccountId.Name
ShowFields[i] נקודות, בלי שם הטבלה "AccountId.Name"
OptionalFields[i].field זהה ל‑ShowFields[i] "AccountId.Name"
OptionalFields[i].aggrField מסלול מלא עם שם הטבלה "AccountId.Accounts.Name"

הטעות החוזרת: field: "AccountId" (אובייקט ה‑Pointer) במקום field: "AccountId.Name" (המחרוזת המוצגת). בשדות _User שימו לב ל‑name באות קטנה: ShowFields: "OwnerId.name" · aggrField: "OwnerId._User.name".

תמיד Get-Optional-Fields(className) קודם, ומעתיקים משם את הערכים המדויקים.

GridDivider תמיד 3 (=4 בשורה) או 4 (=3 בשורה); 6 ו‑12 יוצרים פילטרים מתוחים על כל רוחב העמוד. גם בדוח עם שני פילטרים, תאים ריקים בסדר.

סדר הפילטרים (מימין לשמאל, מלמעלה למטה): חיפוש טקסט ← בחירה מרובה של Pointer (סטטוס, סוג, אחראי) ← זוגות טווח תאריכים ← צ’קבוקסים בוליאניים תמיד אחרונים (נרנדרים בפינה השמאלית‑תחתונה).

Group object must contain values — קיבוץ לפי שדה שהוא NULL בחלק מהרשומות; צינור האגרגציה לא יכול לייצר מפתח קיבוץ מערך חסר. התיקון: פילטר exists על שדה הקיבוץ, כברירת מחדל:

JSON
{ "QueryElems": [
{ "F": "Industry", "C": "exists", "T": "String", "FText": "תעשייה קיימת" }
]}

לשדה Pointer: {"F": "OwnerId", "C": "exists", "T": "Pointer", "P": {"targetClass": "_User"}}. המשתמש יכול לבטל את הפילטר מהממשק כדי לראות גם רשומות בלי הערך.

תוצאות מצטברות ריקות — לפי הסדר: (1) field תואם ל‑ShowFields? (2) aggrField כולל את שם המחלקה? (3) יש רשומות שתואמות לערכי ברירת המחדל של ה‑QueryElems?

  • הרשאות נדרשות ביצירה — role:<RoleName> לתפקידים, objectId למשתמש ספציפי.
  • editPermissions נדרש כשעובדים עם master key — objectId של משתמש תקף.
  • FormName נדרש לשאילתה על דף ישות — לוקחים אותו משאילתה קיימת באותו דף.
  • מיון יורד עם מינוס: -createdAt.
  • QueryElems מסוג Pointer דורשים אובייקט P עם targetClass (ו‑multiple: true ל‑containedIn).
  • טווחי תאריכים בזוגות: greaterThanOrEqualTo + lessThanOrEqualTo.