complexqa_frontend_core 1.26.1 → 1.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "complexqa_frontend_core",
3
- "version": "1.26.1",
3
+ "version": "1.27.1",
4
4
  "description": "core of web ",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,16 @@
1
+ import { ApiAbstractReferenceClass } from '../api_abstract_reference_class.js';
2
+
3
+ /**
4
+ * System custom property templates (read-only for tenants).
5
+ *
6
+ * @version v.0.1 (15/08/2026)
7
+ */
8
+ export class CustomPropertyTemplateApi extends ApiAbstractReferenceClass
9
+ {
10
+ module_prefix = 'custom_property_template';
11
+
12
+ constructor(options)
13
+ {
14
+ super(options);
15
+ }
16
+ }
@@ -0,0 +1,54 @@
1
+ import { ApiAbstractElementClass } from './api_abstract_element_class.js';
2
+ import { typeCustomProperty } from '../types/family_elements/typeCustomProperty.js';
3
+
4
+ /**
5
+ * CRUD / Search / clone_from_templates for custom_property catalog.
6
+ * Create / update / delete / clone require ADMINISTRATOR on the server.
7
+ *
8
+ * @version v.0.1 (15/08/2026)
9
+ */
10
+ export class CustomPropertyApi extends ApiAbstractElementClass
11
+ {
12
+ module_prefix = 'custom_property';
13
+
14
+ constructor(options)
15
+ {
16
+ super(options);
17
+ this.set_element_class_instance(typeCustomProperty);
18
+ }
19
+
20
+
21
+ /**
22
+ * Clone system templates into the team catalog for one element_type.
23
+ * Server: ADMINISTRATOR only.
24
+ *
25
+ * @param {{ element_type: string, custom_property_template_ids: number[] }} payload
26
+ * @returns {Promise<{response: *, payload: *}>}
27
+ */
28
+ async clone_from_templates(payload)
29
+ {
30
+ if (!this.module_prefix)
31
+ {
32
+ throw new Error('Error #custom-property-clone-1152 in CustomPropertyApi');
33
+ }
34
+
35
+ if (this.stage === 'development')
36
+ {
37
+ return {
38
+ response: payload,
39
+ payload : payload,
40
+ };
41
+ }
42
+
43
+ let url = `/${ this.api_prefix }/${ this.module_prefix }/clone_from_templates`;
44
+
45
+ payload = this.handle_request_payload(payload);
46
+ let response = this.api_request(url, payload);
47
+ response = this.handle_response(response);
48
+
49
+ return {
50
+ response: response,
51
+ payload : payload,
52
+ };
53
+ }
54
+ }
@@ -0,0 +1,17 @@
1
+ import { ApiAbstractElementClass } from './api_abstract_element_class.js';
2
+
3
+ /**
4
+ * Options for DROPDOWN / MULTISELECT catalog slots.
5
+ * Mutations require ADMINISTRATOR on the server.
6
+ *
7
+ * @version v.0.1 (15/08/2026)
8
+ */
9
+ export class CustomPropertyOptionApi extends ApiAbstractElementClass
10
+ {
11
+ module_prefix = 'custom_property_option';
12
+
13
+ constructor(options)
14
+ {
15
+ super(options);
16
+ }
17
+ }
@@ -0,0 +1,82 @@
1
+ import { ApiAbstractElementClass } from './api_abstract_element_class.js';
2
+ import { typeCustomPropertyValue } from '../types/family_relation/typeCustomPropertyValue.js';
3
+
4
+ /**
5
+ * Explicit value writers: set / add / delete. Not a union create().
6
+ * READONLY → api_code = -501. MEMBER and ADMINISTRATOR may write.
7
+ *
8
+ * @version v.0.1 (15/08/2026)
9
+ */
10
+ export class CustomPropertyValueApi extends ApiAbstractElementClass
11
+ {
12
+ module_prefix = 'custom_property_value';
13
+
14
+ constructor(options)
15
+ {
16
+ super(options);
17
+ this.set_element_class_instance(typeCustomPropertyValue);
18
+ }
19
+
20
+
21
+ /**
22
+ * @param {object} payload
23
+ * @returns {Promise<{response: *, payload: *}>}
24
+ */
25
+ async set(payload)
26
+ {
27
+ return this.#process('set', payload);
28
+ }
29
+
30
+
31
+ /**
32
+ * @param {object} payload
33
+ * @returns {Promise<{response: *, payload: *}>}
34
+ */
35
+ async add(payload)
36
+ {
37
+ return this.#process('add', payload);
38
+ }
39
+
40
+
41
+ /**
42
+ * @param {object} payload
43
+ * @returns {Promise<{response: *, payload: *}>}
44
+ */
45
+ async delete(payload)
46
+ {
47
+ return this.#process('delete', payload);
48
+ }
49
+
50
+
51
+ /**
52
+ * @param {string} action
53
+ * @param {object} payload
54
+ * @returns {Promise<{response: *, payload: *}>}
55
+ */
56
+ async #process(action, payload)
57
+ {
58
+ if (!this.module_prefix)
59
+ {
60
+ throw new Error('Error #custom-property-value-1152 in CustomPropertyValueApi');
61
+ }
62
+
63
+ if (this.stage === 'development')
64
+ {
65
+ return {
66
+ response: payload,
67
+ payload : payload,
68
+ };
69
+ }
70
+
71
+ let url = `/${ this.api_prefix }/${ this.module_prefix }/${ action }`;
72
+
73
+ payload = this.handle_request_payload(payload);
74
+ let response = this.api_request(url, payload);
75
+ response = this.handle_response(response);
76
+
77
+ return {
78
+ response: response,
79
+ payload : payload,
80
+ };
81
+ }
82
+ }
@@ -16,8 +16,12 @@ import { BugRelationApi } from "./bug_relation_api.js";
16
16
  import { AttachmentApi } from "./attachment_api.js";
17
17
  import { TagApi } from "./tag_api.js";
18
18
  import { TagRelationApi } from "./tag_relation_api.js";
19
+ import { CustomPropertyApi } from "./custom_property_api.js";
20
+ import { CustomPropertyValueApi } from "./custom_property_value_api.js";
21
+ import { CustomPropertyOptionApi } from "./custom_property_option_api.js";
19
22
  import { TagColorApi } from "./tag/tag_color_api.js";
20
23
  import { TagTemplateApi } from "./tag/tag_template_api.js";
24
+ import { CustomPropertyTemplateApi } from "./custom_property/custom_property_template_api.js";
21
25
  import { ContextBrowserApi } from "./context/context_browser_api.js";
22
26
  import { ContextDeploymentTargetApi } from "./context/context_deployment_target_api.js";
23
27
  import { ContextDevicesApi } from "./context/context_devices_api.js";
@@ -176,6 +180,9 @@ export class Api
176
180
  this.attachment = new AttachmentApi(payload);
177
181
  this.tag = new TagApi(payload);
178
182
  this.tag_relation = new TagRelationApi(payload);
183
+ this.custom_property = new CustomPropertyApi(payload);
184
+ this.custom_property_value = new CustomPropertyValueApi(payload);
185
+ this.custom_property_option = new CustomPropertyOptionApi(payload);
179
186
  this.event = new EventApi(payload);
180
187
  this.execution_context = new ExecutionContextApi(payload);
181
188
  this.browser_tools_util_execution = new BrowserToolsUtilExecutionApi(payload);
@@ -208,6 +215,7 @@ export class Api
208
215
  response.context_screen_resolution = new ContextScreenResolutionApi(payload);
209
216
  response.tag_color = new TagColorApi(payload);
210
217
  response.tag_template = new TagTemplateApi(payload);
218
+ response.custom_property_template = new CustomPropertyTemplateApi(payload);
211
219
 
212
220
  return response;
213
221
  }
@@ -4,8 +4,10 @@ import { typeTestRun } from '../../../types/family_elements/typeTestRun.js';
4
4
  import { typeBug } from '../../../types/family_elements/typeBug.js';
5
5
  import { typeBugRelation } from '../../../types/family_relation/typeBugRelation.js';
6
6
  import { typeTag } from '../../../types/family_elements/typeTag.js';
7
+ import { typeCustomProperty } from '../../../types/family_elements/typeCustomProperty.js';
7
8
  import { typeAttachment } from '../../../types/family_elements/typeAttachment.js';
8
9
  import { typeTagRelation } from '../../../types/family_relation/typeTagRelation.js';
10
+ import { typeCustomPropertyValue } from '../../../types/family_relation/typeCustomPropertyValue.js';
9
11
  import { typeTestRunResult } from '../../../types/family_elements/typeTestRunResult.js';
10
12
  import { typeTestAccount } from '../../../types/family_elements/typeTestAccount.js';
11
13
  import { typeExecutionContext } from '../../../types/family_context/typeExecutionContext.js';
@@ -39,6 +41,8 @@ export const TABLE_ELEMENT_MODELS = {
39
41
  attachment : typeAttachment,
40
42
  tag : typeTag,
41
43
  tag_relation : typeTagRelation,
44
+ custom_property: typeCustomProperty,
45
+ custom_property_value: typeCustomPropertyValue,
42
46
  test_run_result: typeTestRunResult,
43
47
  test_account : typeTestAccount,
44
48
  execution_context : typeExecutionContext,
@@ -0,0 +1,96 @@
1
+ import { typeCustomProperty } from '../../../types/family_elements/typeCustomProperty.js';
2
+ import { UserService } from '../../../services/UserService.js';
3
+ import {
4
+ createColumn,
5
+ createGoToColumn,
6
+ createPrimaryKeyColumn,
7
+ createRowSelectColumn,
8
+ } from '../helpers/column_factory.js';
9
+ import { createListingFor, registerSection } from '../helpers/section_registry.js';
10
+ import { typeTableColumn } from '../../../types/family_service/typeTableColumn.js';
11
+
12
+ function build_custom_property_listing_columns(locale)
13
+ {
14
+ const model = new typeCustomProperty();
15
+
16
+ return [
17
+ createRowSelectColumn(),
18
+ createPrimaryKeyColumn(model, locale),
19
+ createColumn(model, locale, 'name', {
20
+ width : '240',
21
+ is_editable: true,
22
+ is_required: true,
23
+ is_title : true,
24
+ }),
25
+ createColumn(model, locale, 'property_key', { width: '180' }),
26
+ createColumn(model, locale, 'element_type', { width: '140' }),
27
+ createColumn(model, locale, 'value_type', { width: '150' }),
28
+ createColumn(model, locale, 'cardinality', { width: '140', is_editable: true }),
29
+ createColumn(model, locale, 'is_enabled', { width: '110', is_editable: true }),
30
+ createColumn(model, locale, 'is_required', { width: '110', is_editable: true }),
31
+ createColumn(model, locale, 'is_searchable', { width: '120', is_editable: true }),
32
+ createColumn(model, locale, 'created_at', { width: '170' }),
33
+ createColumn(model, locale, 'updated_at', { width: '170' }),
34
+ createGoToColumn(model, locale),
35
+ ];
36
+ }
37
+
38
+
39
+ function build_custom_property_details_columns(locale)
40
+ {
41
+ const model = new typeCustomProperty();
42
+ const fields = [
43
+ 'custom_property_id',
44
+ 'team_id',
45
+ 'element_type',
46
+ 'property_key',
47
+ 'value_type',
48
+ 'cardinality',
49
+ 'name',
50
+ 'is_required',
51
+ 'is_enabled',
52
+ 'is_searchable',
53
+ 'sort_order',
54
+ 'created_at',
55
+ 'updated_at',
56
+ ];
57
+
58
+ return [
59
+ ...fields.map((field, index) => createColumn(model, locale, field, {
60
+ sort_order : String(index + 1),
61
+ is_editable: [ 'name', 'cardinality', 'is_required', 'is_enabled', 'is_searchable', 'sort_order' ].includes(field),
62
+ is_sortable: false,
63
+ is_filter : false,
64
+ })),
65
+ new typeTableColumn({
66
+ header_name: 'copy',
67
+ field : 'copy',
68
+ width : '80',
69
+ sort_order : '99',
70
+ is_sortable: false,
71
+ is_filter : false,
72
+ is_editable: false,
73
+ column_kind: 'copy',
74
+ }),
75
+ ];
76
+ }
77
+
78
+
79
+ /**
80
+ * Секции `custom_property`: `listing`, `details`.
81
+ *
82
+ * @param {Object} config
83
+ * @returns {void}
84
+ */
85
+ export function bootstrap_custom_property(config)
86
+ {
87
+ const locale = UserService.get_current_language();
88
+
89
+ registerSection(config, 'custom_property', 'listing', build_custom_property_listing_columns(locale), {
90
+ for: createListingFor([], { add: [ 'options' ] }),
91
+ });
92
+
93
+ registerSection(config, 'custom_property', 'details', build_custom_property_details_columns(locale), {
94
+ for: createListingFor([ 'custom_property_id' ], { add: [ 'options' ] }),
95
+ });
96
+ }
@@ -3,6 +3,7 @@ import { bootstrap_bug } from './sections/bug.js';
3
3
  import { bootstrap_bug_relation } from './sections/bug_relation.js';
4
4
  import { bootstrap_tag } from './sections/tag.js';
5
5
  import { bootstrap_tag_relation } from './sections/tag_relation.js';
6
+ import { bootstrap_custom_property } from './sections/custom_property.js';
6
7
  import { bootstrap_test_case } from './sections/test_case.js';
7
8
  import { bootstrap_test_case_step } from './sections/test_case_step.js';
8
9
  import { bootstrap_test_run } from './sections/test_run.js';
@@ -117,6 +118,7 @@ export class TableConfiguration extends abstractAppConfiguration
117
118
  bootstrap_bug_relation(this.config);
118
119
  bootstrap_tag(this.config);
119
120
  bootstrap_tag_relation(this.config);
121
+ bootstrap_custom_property(this.config);
120
122
  bootstrap_test_case(this.config);
121
123
  bootstrap_test_case_step(this.config);
122
124
  bootstrap_test_run(this.config);
package/publish/index.js CHANGED
@@ -17,11 +17,13 @@ export { typeTestRunResult } from './types/family_elements/typeTestRunResult.js'
17
17
  export { typeTestAccount } from './types/family_elements/typeTestAccount.js'
18
18
  export {typeBug} from './types/family_elements/typeBug.js';
19
19
  export { typeTag } from './types/family_elements/typeTag.js';
20
+ export { typeCustomProperty } from './types/family_elements/typeCustomProperty.js';
20
21
  export { typeAttachment } from './types/family_elements/typeAttachment.js';
21
22
 
22
23
  export { familyRelation } from './types/core/1_familyRelation.js';
23
24
  export { typeBugRelation } from './types/family_relation/typeBugRelation.js';
24
25
  export { typeTagRelation } from './types/family_relation/typeTagRelation.js';
26
+ export { typeCustomPropertyValue } from './types/family_relation/typeCustomPropertyValue.js';
25
27
 
26
28
  export { familyContext } from './types/core/1_familyContext.js';
27
29
  export { typeBrowserContext } from './types/family_context/typeBrowserContext.js';
@@ -44,6 +44,15 @@ export class UserService extends abstractService
44
44
  static current_team_slug = false;
45
45
 
46
46
 
47
+ /**
48
+ * member_role of the current user in current_team_id: ADMINISTRATOR | MEMBER | READONLY.
49
+ * SPA bootstrap: auth/common_page.blade.php from team_members join in get_user_teams().
50
+ *
51
+ * @type {false|string}
52
+ */
53
+ static current_member_role = false;
54
+
55
+
47
56
  /**
48
57
  *
49
58
  * @type {false|array<typeTeam>}
@@ -16,6 +16,14 @@ export class familyTestDocumentation extends familyGeneralElement
16
16
  */
17
17
  tags;
18
18
 
19
+ /**
20
+ * Hydrated on element search via options.add: ['custom_properties'].
21
+ * Catalog entities leave this unset.
22
+ *
23
+ * @type {Array<object>|undefined}
24
+ */
25
+ custom_properties;
26
+
19
27
 
20
28
  /**
21
29
  *
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Переводы для typeCustomProperty.
3
+ *
4
+ * @see typeCustomProperty
5
+ */
6
+ export const typeCustomProperty_i18n = {
7
+ attribute_name_translate_matrix: {
8
+ en: {
9
+ custom_property_id: 'ID',
10
+ team_id : 'Team',
11
+ element_type : 'Element type',
12
+ property_key : 'Key',
13
+ value_type : 'Value type',
14
+ cardinality : 'Cardinality',
15
+ name : 'Name',
16
+ description : 'Description',
17
+ is_required : 'Required',
18
+ is_enabled : 'Enabled',
19
+ is_searchable : 'Searchable',
20
+ sort_order : 'Sort order',
21
+ created_at : 'Created',
22
+ updated_at : 'Updated',
23
+ },
24
+ ru: {
25
+ custom_property_id: 'ID',
26
+ team_id : 'Команда',
27
+ element_type : 'Тип элемента',
28
+ property_key : 'Ключ',
29
+ value_type : 'Тип значения',
30
+ cardinality : 'Кардинальность',
31
+ name : 'Название',
32
+ description : 'Описание',
33
+ is_required : 'Обязательное',
34
+ is_enabled : 'Включено',
35
+ is_searchable : 'Искать',
36
+ sort_order : 'Порядок',
37
+ created_at : 'Создано',
38
+ updated_at : 'Обновлено',
39
+ },
40
+ },
41
+ enum_value_translate_matrix: {
42
+ en: {
43
+ element_type: {
44
+ test_case: 'Test case',
45
+ bug : 'Bug',
46
+ },
47
+ value_type: {
48
+ STRING : 'String',
49
+ TEXT : 'Text',
50
+ INTEGER : 'Integer',
51
+ DATE : 'Date',
52
+ URL : 'URL',
53
+ USER : 'User',
54
+ DROPDOWN : 'Dropdown',
55
+ CHECKBOX : 'Checkbox',
56
+ MULTISELECT: 'Multi-select',
57
+ },
58
+ cardinality: {
59
+ SINGLE : 'Single',
60
+ MULTIPLE: 'Multiple',
61
+ },
62
+ },
63
+ ru: {
64
+ element_type: {
65
+ test_case: 'Тест-кейс',
66
+ bug : 'Ошибка',
67
+ },
68
+ value_type: {
69
+ STRING : 'Строка',
70
+ TEXT : 'Текст',
71
+ INTEGER : 'Целое',
72
+ DATE : 'Дата',
73
+ URL : 'URL',
74
+ USER : 'Пользователь',
75
+ DROPDOWN : 'Список',
76
+ CHECKBOX : 'Флажок',
77
+ MULTISELECT: 'Множественный выбор',
78
+ },
79
+ cardinality: {
80
+ SINGLE : 'Одно значение',
81
+ MULTIPLE: 'Несколько значений',
82
+ },
83
+ },
84
+ },
85
+ };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Переводы для typeCustomPropertyValue.
3
+ *
4
+ * @see typeCustomPropertyValue
5
+ */
6
+ export const typeCustomPropertyValue_i18n = {
7
+ attribute_name_translate_matrix: {
8
+ en: {
9
+ custom_property_value_id: 'ID',
10
+ custom_property_id : 'Custom property',
11
+ team_id : 'Team',
12
+ project_id : 'Project',
13
+ element_type : 'Element type',
14
+ element_id : 'Element',
15
+ sort_order : 'Sort order',
16
+ user_id : 'User',
17
+ created_at : 'Created',
18
+ updated_at : 'Updated',
19
+ string_value : 'String',
20
+ text_value : 'Text',
21
+ integer_value : 'Integer',
22
+ date_value : 'Date',
23
+ url : 'URL',
24
+ url_caption : 'Caption',
25
+ user_id_value : 'User',
26
+ checkbox_value : 'Checked',
27
+ option_id : 'Option',
28
+ },
29
+ ru: {
30
+ custom_property_value_id: 'ID',
31
+ custom_property_id : 'Свойство',
32
+ team_id : 'Команда',
33
+ project_id : 'Проект',
34
+ element_type : 'Тип элемента',
35
+ element_id : 'Элемент',
36
+ sort_order : 'Порядок',
37
+ user_id : 'Пользователь',
38
+ created_at : 'Создано',
39
+ updated_at : 'Обновлено',
40
+ string_value : 'Строка',
41
+ text_value : 'Текст',
42
+ integer_value : 'Целое',
43
+ date_value : 'Дата',
44
+ url : 'URL',
45
+ url_caption : 'Подпись',
46
+ user_id_value : 'Пользователь',
47
+ checkbox_value : 'Флажок',
48
+ option_id : 'Опция',
49
+ },
50
+ },
51
+ enum_value_translate_matrix: {
52
+ en: {
53
+ element_type: {
54
+ test_case: 'Test case',
55
+ bug : 'Bug',
56
+ },
57
+ },
58
+ ru: {
59
+ element_type: {
60
+ test_case: 'Тест-кейс',
61
+ bug : 'Ошибка',
62
+ },
63
+ },
64
+ },
65
+ };
@@ -16,6 +16,7 @@ export const typeTeamTotalStatistic_i18n = {
16
16
  bug : 'Bugs',
17
17
  bug_relation : 'Bug relations',
18
18
  tag : 'Tags',
19
+ custom_property : 'Custom properties',
19
20
  execution_context : 'Execution contexts',
20
21
  task : 'Tasks',
21
22
  team_member : 'Team members',
@@ -37,6 +38,7 @@ export const typeTeamTotalStatistic_i18n = {
37
38
  bug : 'Баги',
38
39
  bug_relation : 'Связи багов',
39
40
  tag : 'Теги',
41
+ custom_property : 'Пользовательские свойства',
40
42
  execution_context : 'Контексты выполнения',
41
43
  task : 'Задачи',
42
44
  team_member : 'Участники команды',
@@ -58,6 +60,7 @@ export const typeTeamTotalStatistic_i18n = {
58
60
  bug : 'Fehler',
59
61
  bug_relation : 'Fehlerverknüpfung',
60
62
  tag : 'Tag',
63
+ custom_property : 'Benutzerdefinierte Eigenschaften',
61
64
  execution_context : 'Ausführungskontext',
62
65
  task : 'Aufgabe',
63
66
  team_member : 'Teammitglied',
@@ -79,6 +82,7 @@ export const typeTeamTotalStatistic_i18n = {
79
82
  bug : 'خطأ',
80
83
  bug_relation : 'علاقة الخطأ',
81
84
  tag : 'وسم',
85
+ custom_property : 'خصائص مخصصة',
82
86
  execution_context : 'سياق التنفيذ',
83
87
  task : 'مهمة',
84
88
  team_member : 'عضو الفريق',
@@ -100,6 +104,7 @@ export const typeTeamTotalStatistic_i18n = {
100
104
  bug : 'Error',
101
105
  bug_relation : 'Relación de error',
102
106
  tag : 'Etiqueta',
107
+ custom_property : 'Propiedades personalizadas',
103
108
  execution_context : 'Contexto de ejecución',
104
109
  task : 'Tarea',
105
110
  team_member : 'Miembro del equipo',
@@ -121,6 +126,7 @@ export const typeTeamTotalStatistic_i18n = {
121
126
  bug : 'Bug',
122
127
  bug_relation : 'Relation de bug',
123
128
  tag : 'Tag',
129
+ custom_property : 'Propriétés personnalisées',
124
130
  execution_context : 'Contexte d\'exécution',
125
131
  task : 'Tâche',
126
132
  team_member : 'Membre de l\'équipe',
@@ -142,6 +148,7 @@ export const typeTeamTotalStatistic_i18n = {
142
148
  bug : 'Սխալ',
143
149
  bug_relation : 'Սխալի կապ',
144
150
  tag : 'Պիտակ',
151
+ custom_property : 'Հատուկ հատկություններ',
145
152
  execution_context : 'Կատարման համատեքստ',
146
153
  task : 'Առաջադրանք',
147
154
  team_member : 'Թիմի անդամ',
@@ -163,6 +170,7 @@ export const typeTeamTotalStatistic_i18n = {
163
170
  bug : 'Bug',
164
171
  bug_relation : 'Relazione bug',
165
172
  tag : 'Tag',
173
+ custom_property : 'Proprietà personalizzate',
166
174
  execution_context : 'Contesto di esecuzione',
167
175
  task : 'Attività',
168
176
  team_member : 'Membro del team',
@@ -184,6 +192,7 @@ export const typeTeamTotalStatistic_i18n = {
184
192
  bug : 'Błąd',
185
193
  bug_relation : 'Powiązanie błędu',
186
194
  tag : 'Tag',
195
+ custom_property : 'Właściwości niestandardowe',
187
196
  execution_context : 'Kontekst wykonania',
188
197
  task : 'Zadanie',
189
198
  team_member : 'Członek zespołu',
@@ -205,6 +214,7 @@ export const typeTeamTotalStatistic_i18n = {
205
214
  bug : 'Bug',
206
215
  bug_relation : 'Relação de bug',
207
216
  tag : 'Tag',
217
+ custom_property : 'Propriedades personalizadas',
208
218
  execution_context : 'Contexto de execução',
209
219
  task : 'Tarefa',
210
220
  team_member : 'Membro da equipa',
@@ -226,6 +236,7 @@ export const typeTeamTotalStatistic_i18n = {
226
236
  bug : 'Хата',
227
237
  bug_relation : 'Хата бәйләнеше',
228
238
  tag : 'Тег',
239
+ custom_property : 'Кулланучы үзлекләре',
229
240
  execution_context : 'Башкару контексты',
230
241
  task : 'Бурыч',
231
242
  team_member : 'Команд әгъзасы',
@@ -0,0 +1,138 @@
1
+ import { familyTestDocumentation } from "../core/1_familyTestDocumentation.js";
2
+ import { is_object } from "../../utils/utils.js";
3
+ import _ from 'underscore';
4
+ import { typeCustomProperty_i18n } from "../element_translate/typeCustomProperty.i18n.js";
5
+
6
+ /**
7
+ * Team-scoped custom property catalog slot for one element_type (test_case | bug).
8
+ * Server CUD requires member_role = ADMINISTRATOR. Do not set clone_element = false (listing CM).
9
+ *
10
+ * @see docs/CustomProperties/custom-property-docs.MD
11
+ *
12
+ * @version v.0.1 (15/08/2026)
13
+ */
14
+ export class typeCustomProperty extends familyTestDocumentation
15
+ {
16
+ custom_property_id;
17
+ team_id;
18
+ element_type;
19
+ property_key;
20
+ value_type;
21
+ cardinality;
22
+ name;
23
+ description;
24
+ is_required;
25
+ is_enabled;
26
+ is_searchable;
27
+ sort_order;
28
+ created_at;
29
+ updated_at;
30
+
31
+ /** Hydrated via search options.add: ['options'] */
32
+ options;
33
+
34
+ primary_key = 'custom_property_id';
35
+ api_key = 'custom_property';
36
+
37
+ structure_scheme = {
38
+ custom_property_id: 'integer | require',
39
+ team_id : 'integer | require',
40
+ element_type : 'string | enum | require',
41
+ property_key : 'string | require',
42
+ value_type : 'string | enum | require',
43
+ cardinality : 'string | enum | require',
44
+ name : 'string | require',
45
+ description : 'string | optional',
46
+ is_required : 'integer | optional',
47
+ is_enabled : 'integer | optional',
48
+ is_searchable : 'integer | optional',
49
+ sort_order : 'integer | optional',
50
+ created_at : 'timestamp | optional',
51
+ updated_at : 'timestamp | optional',
52
+ };
53
+
54
+ create_scheme = [
55
+ 'element_type',
56
+ 'property_key',
57
+ 'value_type',
58
+ 'name',
59
+ 'cardinality',
60
+ 'description',
61
+ 'is_required',
62
+ 'is_enabled',
63
+ 'is_searchable',
64
+ 'sort_order',
65
+ ];
66
+
67
+ available_enum_values = {
68
+ element_type: typeCustomProperty.get_element_type_values(),
69
+ value_type : typeCustomProperty.get_value_type_values(),
70
+ cardinality : typeCustomProperty.get_cardinality_values(),
71
+ };
72
+
73
+ enum_value_translate_matrix = typeCustomProperty_i18n.enum_value_translate_matrix;
74
+
75
+ attribute_name_translate_matrix = typeCustomProperty_i18n.attribute_name_translate_matrix;
76
+
77
+
78
+ static get_element_type_values()
79
+ {
80
+ return [ 'test_case', 'bug' ];
81
+ }
82
+
83
+
84
+ static get_value_type_values()
85
+ {
86
+ return [
87
+ 'STRING',
88
+ 'TEXT',
89
+ 'INTEGER',
90
+ 'DATE',
91
+ 'URL',
92
+ 'USER',
93
+ 'DROPDOWN',
94
+ 'CHECKBOX',
95
+ 'MULTISELECT',
96
+ ];
97
+ }
98
+
99
+
100
+ static get_cardinality_values()
101
+ {
102
+ return [ 'SINGLE', 'MULTIPLE' ];
103
+ }
104
+
105
+
106
+ /**
107
+ * @param {object|false|undefined} data
108
+ * @returns {typeCustomProperty}
109
+ */
110
+ constructor(data = false)
111
+ {
112
+ super();
113
+
114
+ if (data && is_object(data))
115
+ {
116
+ _.mapObject(data, (value, key) =>
117
+ {
118
+ if (this.hasOwnProperty(key))
119
+ {
120
+ this[ key ] = value;
121
+ }
122
+ });
123
+ }
124
+
125
+ return this;
126
+ }
127
+
128
+
129
+ get_element_name()
130
+ {
131
+ if (this.name)
132
+ {
133
+ return String(this.name);
134
+ }
135
+
136
+ return String(this.property_key ?? this.custom_property_id ?? '');
137
+ }
138
+ }
@@ -40,6 +40,7 @@ export class familyEvent extends familyGeneralElement
40
40
 
41
41
  /**
42
42
  * Каталог subject_type (= api_key), совпадает с seed event-service.
43
+ * `custom_property` — CUD слота каталога; значения на хосте публикуются как test_case/bug UPDATE.
43
44
  *
44
45
  * @returns {string[]}
45
46
  */
@@ -61,6 +62,7 @@ export class familyEvent extends familyGeneralElement
61
62
  'attachment',
62
63
  'tag',
63
64
  'tag_relation',
65
+ 'custom_property',
64
66
  'test_account',
65
67
  'user_profile',
66
68
  'text_document',
@@ -0,0 +1,117 @@
1
+ import { is_object } from '../../utils/utils.js';
2
+ import _ from 'underscore';
3
+ import { familyRelation } from '../core/1_familyRelation.js';
4
+ import { typeCustomPropertyValue_i18n } from '../element_translate/typeCustomPropertyValue.i18n.js';
5
+
6
+ /**
7
+ * One typed value row on a host element (test_case | bug).
8
+ * Mutations go through CustomPropertyValueApi.set / add / delete, not union create().
9
+ * Server rejects member_role = READONLY (api_code = -501). MEMBER may write.
10
+ *
11
+ * @see docs/CustomProperties/custom-property-docs.MD
12
+ *
13
+ * @version v.0.1 (15/08/2026)
14
+ */
15
+ export class typeCustomPropertyValue extends familyRelation
16
+ {
17
+ custom_property_value_id;
18
+ custom_property_id;
19
+ team_id;
20
+ project_id;
21
+ element_type;
22
+ element_id;
23
+ sort_order;
24
+ user_id;
25
+ created_at;
26
+ updated_at;
27
+
28
+ string_value;
29
+ text_value;
30
+ integer_value;
31
+ date_value;
32
+ url;
33
+ url_caption;
34
+ user_id_value;
35
+ checkbox_value;
36
+ option_id;
37
+
38
+ source_key = 'custom_property_id';
39
+ target_key = 'element_id';
40
+ relation_type_key = 'element_type';
41
+
42
+ primary_key = 'custom_property_value_id';
43
+ api_key = 'custom_property_value';
44
+
45
+ static relation_target_map = {
46
+ test_case: { type_token: 'typeTestCase', primary_key: 'test_case_id', api_key: 'test_case' },
47
+ bug : { type_token: 'typeBug', primary_key: 'bug_id', api_key: 'bug' },
48
+ };
49
+
50
+ structure_scheme = {
51
+ custom_property_value_id: 'integer | require',
52
+ custom_property_id : 'integer | typeCustomProperty | require',
53
+ team_id : 'integer | require',
54
+ project_id : 'integer | require',
55
+ element_type : 'string | enum | require',
56
+ element_id : 'integer | require',
57
+ sort_order : 'integer | optional',
58
+ user_id : 'integer | optional',
59
+ created_at : 'timestamp | optional',
60
+ updated_at : 'timestamp | optional',
61
+ };
62
+
63
+ create_scheme = [
64
+ 'custom_property_id',
65
+ 'element_type',
66
+ 'element_id',
67
+ ];
68
+
69
+ available_enum_values = {
70
+ element_type: typeCustomPropertyValue.get_relation_type_values(),
71
+ };
72
+
73
+ enum_value_translate_matrix = typeCustomPropertyValue_i18n.enum_value_translate_matrix;
74
+
75
+ attribute_name_translate_matrix = typeCustomPropertyValue_i18n.attribute_name_translate_matrix;
76
+
77
+
78
+ static get_relation_type_values()
79
+ {
80
+ return Object.keys(typeCustomPropertyValue.relation_target_map);
81
+ }
82
+
83
+
84
+ static get_relation_target_map()
85
+ {
86
+ return typeCustomPropertyValue.relation_target_map;
87
+ }
88
+
89
+
90
+ /**
91
+ * @param {object|false|undefined} data
92
+ * @returns {typeCustomPropertyValue}
93
+ */
94
+ constructor(data = false)
95
+ {
96
+ super();
97
+
98
+ if (data && is_object(data))
99
+ {
100
+ _.mapObject(data, (value, key) =>
101
+ {
102
+ if (this.hasOwnProperty(key))
103
+ {
104
+ this[ key ] = value;
105
+ }
106
+ });
107
+ }
108
+
109
+ return this;
110
+ }
111
+
112
+
113
+ get_relation_target_meta()
114
+ {
115
+ return typeCustomPropertyValue.resolve_relation_target(this.element_type);
116
+ }
117
+ }