דלגו לתוכן

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

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

סקילים — ישויות ושדות

עודכן 30.08.2026

ארבעה סקילים שעובדים על שכבת הנתונים: מה קיים בבסיס הנתונים, איך שדה נראה בטופס, ואיך שדה אחד משפיע על שדה שני. שלושה מהם נשענים על מוסכמות של הפלטפורמה שאי אפשר לנחש — שם השדה הוא ההגדרה.

הסקיל מה נבנה בפועל
myb-p-create-entity מודול שלם: טבלאות ← הרשאות ← נתונים ← כרטיס ← רשימה ← תפריט
myb-p-multi-select-field שדה Array שנרנדר כ‑multi-pick של Select2
myb-p-parent-child-fields דרופ‑דאון בן שמסונן לפי בחירת האב
myb-p-timestamp-field שדה Date + טריגר שממלא אותו

הסקיל הכבד בחבילה. בקשה אחת — “תקים לי מודול ניהול ספקים” — הופכת לחבילה שלמה:

מודול חדש
├── שכבת נתונים
│ ├── טבלה ראשית (Suppliers)
│ ├── טבלת סטטוסים (SupplierStatuses) עם Name + Color
│ ├── טבלאות בנות (SupplierOrders) עם Pointer חזרה
│ └── CLP מבוסס-תפקידים על כל טבלה חדשה
├── שכבת ממשק
│ ├── דף כרטיס (apps/mybusiness/Supplier — יחיד)
│ ├── דף רשימה (apps/mybusiness/Suppliers — רבים)
│ └── פריט תפריט עם אייקון
├── שכבת התנהגות
│ └── קובץ JS חיצוני שמוזרם ל-S3 ומחובר לדף
└── נתוני דמה
└── סטטוסים עם צבעים + רשומות לדוגמה
השלב הכלים
טבלאות Create-Table · Set-Table-Permissions
נתונים Create-Many (עם __type: "Pointer") · Create-Data
דף כרטיס Create-Form-Page · Edit-Page · Get-Page-Content
דף רשימה Create-Table-View-Page · Edit-Table-View · Edit-Page-CSS-JS
JS ותפריט Upload-Public-File · Set-Page-Settings · Get-Page-Settings
שחזור Get-Page-Versions · Set-Page-Version

שדות הסטנדרט שכל ישות מקבלת

Section titled “שדות הסטנדרט שכל ישות מקבלת”
השדה הטיפוס הערה
Name String נוצר אוטומטית על ידי Create-Table
Email / Phone String
StatusId Pointer → <Entity>Statuses נדרש לצביעה לפי סטטוס
OwnerId Pointer → _User האחראי
AccountId Pointer → Accounts רק אם הישות מתחברת ללקוחות
Active Boolean
Comment String שדה שנוסף ב‑Edit-Page נרנדר כ‑<input type="text"> (לא textarea) — ראו דפים וממשק

ערכת הסטטוסים הדיפולטיבית: פעיל #28a745 · ממתין לאישור #ffc107 · מושהה #dc3545 · לא פעיל #6c757d.

שתי מוסכמות שקובעות אם זה יעבוד

Section titled “שתי מוסכמות שקובעות אם זה יעבוד”

1. הרשאות מבוססות‑תפקידים, לא *. CLP עם {"*": true} עובר את שכבת ה‑API (מאומת: קריאה אנונימית ב‑REST מצליחה) — אבל ה‑front-end של ה‑CRM בודק חברות בתפקיד, והמשתמש רואה “אין הרשאות”. טבלה שנוצרת ב‑Create-Table נולדת עם CLP ברירת מחדל שאינו מבוסס תפקידי CRM: find/get ל‑requiresAuthentication, ו‑create/update/delete ל‑role:Admin בלבד (CLP ריק לגמרי נוצר רק ב‑POST /parse/schemas גולמי). לכן Set-Table-Permissions היא קריאה חובה על כל טבלה חדשה, כדי שצוותי CRM/Sales/Support יוכלו לכתוב:

JSON
{
"find": { "role:CRM": true, "role:Admin": true, "role:Support": true },
"get": { "role:CRM": true, "role:Admin": true, "role:Support": true },
"create": { "role:CRM": true, "role:Admin": true, "role:Support": true },
"update": { "role:CRM": true, "role:Admin": true, "role:Support": true },
"delete": { "role:CRM": true, "role:Admin": true, "role:Support": true },
"addField": {}
}

הסכימה של Set-Table-Permissions דורשת את כל שש המפתחות — create, delete, update, find, get, addField. השמטת מפתח פעולה נדחית בוולידציה (addField: expected object). הדריסה השקטה היא בתוך פעולה: תפקיד שלא נכלל ב‑find שנשלח — מאבד את ההרשאה בלי שגיאה.

2. עמודת Pointer שמציגה מחרוזת היא type: "String". זו הטעות שמונעת מהטבלה להיטען בכלל. ה‑type של עמודה הוא הטיפוס של הערך המוצג, לא של הקשר:

JSON
// ✓ נכון — מציג את שם הלקוח דרך Pointer
{
"field": "AccountId.Name",
"type": "String",
"aggrField": "AccountId.Accounts.Name",
"inlineOptions": { "type": "autocomplete", "required": false, "readonly": false }
}
// ✗ שגוי — הטבלה לא תרנדר שורות
{ "field": "AccountId.Name", "type": "Pointer" }

השגיאה שתראו בקונסול: Cannot create property 'Name' on string.

לבחירת ה‑inlineOptions: autocomplete ל‑Pointer שמצביע לטבלה גדולה (Accounts, _User), ו‑select ל‑lookup קטן (סטטוסים, סוגים).

שלוש הדרכים לפתוח כרטיס — אל תערבבו

Section titled “שלוש הדרכים לפתוח כרטיס — אל תערבבו”
המנגנון המסלול מתי הוא חל
כרטיס MasterTicket Mode=Ticket#SideModalTicket ישויות מובנות בלבד (Account / Task / Case). MCP אינו יכול ליצור כרטיסים על המאסטר הזה
כרטיס NewMaster Mode=Modal#SideModal + src של #iframeModal כל כרטיס שנוצר ב‑Create-Form-Page — המסלול של הסקיל הזה
עריכה בשורה editView: { openFrom: "inline" } עריכה מהירה בשורה, בלי כרטיס

הקישור בין הרשימה לכרטיס נעשה ב‑Edit-Table-View:

JSON
"editView": {
"openFrom": "modal-left",
"useIframe": true,
"page": "apps/mybusiness/Supplier",
"popupWidthPercent": 45
}

Create-Form-Page מייצר תמיד דף על בסיס NewMaster, ואין כלי MCP או API לשנות masterPageId של דף קיים — הדפים יושבים באוסף פרטי של Simbla, לא במחלקות Parse. לכן הכרטיס עובד בתוך iframe אבל מציג שדות ריקים כשפותחים אותו ישירות לפי URL: NewMaster לא נופל חזרה לפרמטרים מה‑URL.

שגיאות שחוזרות, וההסבר שלהן

Section titled “שגיאות שחוזרות, וההסבר שלהן”
התסמין הסיבה התיקון
שום דבר ברשימה לא לחיץ, בלי שגיאה הקישוט מכוון לעמודה שלא קיימת (ישות צומת) שנו את {{PRIMARY_COL}} לעמודת התצוגה האמיתית
כפתור “חדש” פותח פאנל ריק #iframeModal לא קיבל src — ה‑data-toggle המורש לבדו פותח מודאל ריק ה‑handler של התבנית: בונה URL ואז modal('show')
לחיצה ← “Page not found” URL יחסי שהוכפל: apps/mybusiness/apps/mybusiness/… לגזור את הבסיס מ‑window.location.pathname, לא לבנות ידנית
הכרטיס נפתח ריק על רשומה קיימת ב‑URL חסר פרמטר cls (יש oid בלבד). Mode=Ticket עצמו אינו האשם: עם oid וגם cls הרשומה נטענת כרגיל (mcp-c-053) לוודא ששני הפרמטרים בקישור. בכרטיס NewMaster לפתוח דרך Mode=Modal#SideModal/#iframeModal
לחיצה פותחת כרטיס של ישות אחרת קובץ ה‑JS המורש (cases.js) עדיין מחובר להחליף בתבנית — להחליף, לא לתקן
הממשק אומר “אין הרשאות” CLP עם * במקום תפקידים, או ACL חסר ברשומות Get-Table-Permissions ← CLP מבוסס‑תפקידים
כותרת הלשונית בדפדפן היא “Cases” Create-Table-View-Page מעתיק גם את ה‑SEO title מהמקור Set-Page-Settings(pageId, title: "<שם הישות>")

שדה שמאחסן מערך של מזהי Pointer ונרנדר כ‑multi-pick של Select2 — בלי שורת JavaScript. הפלטפורמה מזהה את זה משם השדה:

array_<purpose>_Pointer_<TargetTable>
המקטע המשמעות
array_ קידומת באותיות קטנות ← לרנדר כ‑<select multiple>
<purpose> snake_case חופשי (tags, sector, interests). לקריאוּת אנושית — הריצה מתעלמת ממנו
_Pointer_ מפריד מילולי, P גדולה. איברי המערך הם Pointers
<TargetTable> שם המחלקה של טבלת ה‑lookup, תלוי רישיות. ממנו נגזר ה‑targetclass על ה‑<select>
1. Get-Schema(className: "<HostTable>") — הטבלה קיימת? אין התנגשות שם?
2. Create-Table + Create-Many — טבלת ה-lookup, עם 2-3 ערכים לפחות
3. Add-Field-to-Table — השדה עצמו
4. הצבה על דף הטופס — כאן יש פער, ראו למטה
5. אימות — Get-Data על הרשומה
JSON
Add-Field-to-Table({
"table": "Accounts",
"fields": [{
"name": "array_sector_Pointer_AccountTypes",
"type": "Array",
"label": "סקטור בחירה מרובה"
}]
})

הטיפוס חייב להיות Array — לא Pointer ולא String.

הצבה על הדף — ב‑MCP, עם targetClass:

JSON
Edit-Page({ "pageId": "<formPageId>", "actions": [
{ "actionType": "add-new-field", "fieldName": "array_sector_Pointer_AccountTypes",
"label": "סקטור בחירה מרובה", "fieldType": "Array", "targetClass": "AccountTypes",
"toExistingRow": "P272", "toExistingColumn": 0 }
]})

מה יוצא בסוף (מאומת בדפדפן):

HTML
<select class="select-pointer form-control" multiple
id="array_sector_Pointer_AccountTypes"
name="array_sector_Pointer_AccountTypes"
targetclass="AccountTypes"
data-subclass-pointers="[…]"></select>

בזמן ריצה ה‑<select> מאותחל כ‑Select2 מרובה‑בחירה. התווית יושבת ב‑<label> שלצד השדה; ה‑<select> עצמו אינו נושא data-dictionary.

תמיד containedIn, אף פעם לא equalTo:

JSON
{ "F": "array_tags_Pointer_Tags",
"C": "containedIn",
"V": ["zY2p6jpXqm", "io3Z6YsMcG"],
"T": "Array" }

להצגת השמות המחוברים בעמודת דוח — נקודות: array_tags_Pointer_Tags.Tags.Name.

  • קידומת או מפריד שגוייםArray_AccountTypes, array_sector_AccountTypes (חסר _Pointer_), array_sector_pointer_AccountTypes (p קטנה) — כולם נכשלים. ה‑Pointer תלוי רישיות.
  • רישיות של טבלת היעדarray_x_Pointer_accounttypes לא ייפתר.
  • type: "Pointer" במקום "Array" — השדה יאחסן ערך יחיד; ה‑multi-pick ייראה עובד, אבל רק הבחירה האחרונה תישמר.
  • טבלת lookup ריקה — הדרופ‑דאון יציג “אין אפשרויות”. תמיד לזרוע ערכים לפני הדגמה.
  • שינוי שם של שדה מערך אחרי שנשמרו נתונים — הערכים הקיימים מתייתמים; Parse שומר אותם תחת השם הישן. להעביר נתונים לפני שינוי שם.
  • מערכים מבוססי _Dictionary (הסגנון הישן, Sector_By_V) הם מנגנון אחר לגמרי — לא מכוסה כאן.

דרופ‑דאון שמציג רק את הערכים ששייכים לבחירה בשדה אחר על אותו טופס — המנגנון הילידי, בלי JavaScript ובלי חוקי טופס.

שלושה שמות, מנגנון אחד:

איפה השם
עורך הדפים של Simbla Define Parent
פרמטר MCP ב‑Edit-Page subclassDepend
התכונה ב‑HTML המרונדר data-subclass-depend="<ParentTable>"
השלב ב‑MCP? הכלי
יצירה/בדיקה של טבלת האב והבן Create-Table · Get-Schema · Get-Data
שדה Pointer על טבלת הבן ← טבלת האב Add-Field-to-Table
תיוג כל רשומת בן באב שלה Create-Many · Update-Data
שדה האב על דף הטופס Edit-Page (add-new-field, fieldType: "Pointer")
שדה הבן עם ה‑Define Parent מחובר Edit-Page עם subclassDepend
הגדרת Define Parent על שדה שכבר על הדף עוקף: remove-existing-field ואז add-new-field מחדש
קריאה מובנית של ה‑Define Parent הנוכחי עוקף: Get-Page-Content(minimal: false) וחיפוש data-subclass-depend ב‑HTML
JSON
// 1. טבלת הבן, עם Pointer חזרה לאב
Create-Table({
"name": "SalesSubDepartments",
"fields": [{ "name": "SalesDepartmentId", "type": "Pointer",
"targetClass": "SalesDepartments", "label": "מחלקה" }]
})
// 2. תיוג כל רשומת בן — Pointer יחיד, לא Array
Create-Many({ "table": "SalesSubDepartments", "data": [
{ "Name": "B2B-Enterprise", "SalesDepartmentId": {
"__type": "Pointer", "className": "SalesDepartments", "objectId": "L07ZV34Mqu" } }
]})
// 3. שני שדות על טבלת המארח (הטופס)
Add-Field-to-Table({ "table": "Sales", "fields": [
{ "name": "DepartmentId", "type": "Pointer", "targetClass": "SalesDepartments", "label": "מחלקה" },
{ "name": "SubDepartmentId", "type": "Pointer", "targetClass": "SalesSubDepartments", "label": "תת-מחלקה" }
]})
// 4. האב על הדף — Pointer רגיל
Edit-Page({ "pageId": "<formPageId>", "actions": [
{ "actionType": "add-new-field", "fieldName": "DepartmentId", "fieldType": "Pointer",
"targetClass": "SalesDepartments", "toExistingRow": "P272", "toExistingColumn": 0 }
]})
// 5. הבן על הדף — עם המילה הקסומה
Edit-Page({ "pageId": "<formPageId>", "actions": [
{ "actionType": "add-new-field", "fieldName": "SubDepartmentId", "fieldType": "Pointer",
"targetClass": "SalesSubDepartments",
"subclassDepend": "SalesDepartments",
"toExistingRow": "P272", "toExistingColumn": 1 }
]})

זהו. רענון הטופס — והדרופ‑דאון הבן מונע על ידי בחירת האב.

  • Array של Pointers לאב — הסינון עובד בסמנטיקת שוויון על Pointer יחיד. חפיפה (ערך בן שמופיע תחת כמה אבות) אינה נתמכת: משכפלים את השורה, אחת לכל אב.
  • שכחתם subclassDepend ב‑add-new-field — הקריאה מחזירה success בשני המקרים. ההבדל מתגלה רק בזמן ריצה, כשהבן מתעלם מהאב.
  • בן לא מתויג (ה‑Pointer לאב ריק) — לא יופיע תחת שום אב.
  • שני שדות Pointer על הטופס לאותה טבלת אב — הפלטפורמה לא יודעת מי מהם מניע את הסינון.
  • ניסיון לסנן עם חוק טופס — חוקי טופס לא מסננים אפשרויות של Pointer. הפעולות היחידות הן readonly, required, hidden, fixed-value, dynamic-value, formula-value, show-message, value-from-url — ואף אחת מהן אינה סינון. ראו אוטומציה.

שתי דרכים, שתיהן ב‑MCP:

JSON
// א. שכבת הנתונים — הסינון מוגדר נכון?
Get-Data({
"table": "SalesSubDepartments",
"keys": ["Name", "SalesDepartmentId"],
"where": { "SalesDepartmentId": {
"__type": "Pointer", "className": "SalesDepartments", "objectId": "<parentId>" } }
})
// ב. ה-HTML — הקישור על הדף?
Get-Page-Content(pageId, minimal: false) → חפשו data-subclass-depend="SalesDepartments"

שדה Date שמתעד אוטומטית מתי שדה אחר נקבע או השתנה. שלושה כלים, ארבע דקות.

JSON
// 1. השדה
Add-Field-to-Table({ "table": "Sales",
"fields": [{ "name": "OwnerIdSetDate", "type": "Date", "label": "תאריך שיוך אחראי" }] })
// 2. הטריגר — מתי
Set-Trigger({
"tableName": "Sales", "type": "data change", "active": true,
"name": "תיעוד תאריך שיוך אחראי",
"events": ["create", "update"],
"oneachupdate": true,
"onSetFields": ["OwnerId"]
})
// 3. הפעולה — מה
Set-Trigger-Action({
"tableName": "Sales", "triggerId": "<id>", "triggerType": "data change",
"actionType": "update-object",
"actionData": { "update-object": {
"targetClass": "Sales",
"connection": "current.objectId",
"fieldsValue": [{ "field": "OwnerIdSetDate", "type": "dynamic",
"value": "updatedAt", "visibleVal": "updatedAt" }]
}}
})
ההחלטה הפרמטר
כל שינוי, או רק בפעם הראשונה? oneachupdate: true / false
גם ביצירה עם ערך? events: ["create", "update"]
איזה שדה מנוטר? onSetFields: ["OwnerId"]

שדות Date מקבלים רק ערכים מסוג dynamic; updatedAt נותן את הרגע הנוכחי.

חותמת מותנית — רק כשהסטטוס הופך ל“הושלם”:

JSON
"criterias": [{
"F": "SaleStatusId", "C": "equalTo", "V": "<objectId>", "T": "Pointer",
"P": { "targetClass": "SaleStatuses", "visibleVal": "הושלם" }
}]

חותמת על רשומה קשורה"connection": "source.AccountId" מעדכן את הלקוח המקושר במקום את הרשומה הנוכחית.

כמה שדות — טריגר נפרד לכל שדה. טריגר יחיד עם כמה onSetFields יורה על כל אחד מהם אבל כותב לשדה אחד, ולכן הוא עובד רק כ“תאריך שינוי אחרון” גנרי.

טריגרים כן יורים גם מכתיבות API ו‑Master Key (מאומת חי), ולכן בדיקה דרך Update-Data היא בדיקה תקפה:

Update-Data → שינוי השדה המנוטר
Get-Data(table, objectId, keys: ["OwnerIdSetDate", "updatedAt"])

ריק? בדקו את היומן: Get-Data(table: "_syslogTriggers", order: "-createdAt", limit: 3, keys: ["triggerId", "actionId", "error", "event"]) — שורה לכל פעולה, עם error (אין שדה triggerName; where ב‑MCP דורש מפתח fieldNamemcp-data-01).