דלגו לתוכן

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

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

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

עודכן 10.10.2026

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

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

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

מודול חדש
├── שכבת נתונים
│ ├── טבלה ראשית (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 בודק חברות בתפקיד והמשתמש רואה “אין הרשאות”. טבלה שנוצרת ב‑Create-Table נולדת עם CLP ברירת מחדל שאינו מבוסס תפקידי CRM: find/get ל‑requiresAuthentication, create/update/delete ל‑role:Admin בלבד (CLP ריק לגמרי נוצר רק ב‑POST /parse/schemas גולמי). Set-Table-Permissions היא קריאה חובה על כל טבלה חדשה:

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": {}
}

הסכימה דורשת את כל שש המפתחות — 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.

התסמין הסיבה התיקון
שום דבר ברשימה לא לחיץ, בלי שגיאה הקישוט מכוון לעמודה שלא קיימת (ישות צומת) שנו את {{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 הרשומה נטענת כרגיל שני הפרמטרים בקישור. בכרטיס 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. ההצבה על הדף — עם 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. ראו אוטומציה.
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 דורש מפתח fieldName).


ציר הזמן ותיעוד השינויים ברשומה, משני הצדדים: אילו טבלאות כותבות יצירה, עדכון ומחיקה ל‑_Timeline (Get-Timeline-Settings / Set-Timeline-Settings), והאם מישהו רואה את זה — חיבור דף ה‑Timeline המשותף והפיד בכרטיס.

  • התיעוד מופעל לכל טבלה בנפרד: רק 11 טבלאות מגיעות עם תיעוד פעיל, וטבלה מותאמת אינה מתועדת עד שמפעילים.
  • Set-Timeline-Settings מחליף את כל ההגדרה ולא ממזג — קוראים קודם ושולחים את המפה המלאה.
  • הגדרה תקינה לבדה לא מספיקה: בלי חיבור התצוגה בדף, הפיד בכרטיס ריק.

טריגרים: “להפעיל ציר זמן לטבלה”, “ציר הזמן ריק”, “מי שינה את הרשומה”, “audit trail”.