cabloy 5.1.148 → 5.1.149

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.
Files changed (36) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +11 -0
  3. package/package.json +1 -1
  4. package/repo-docs/.vitepress/config.mjs +4 -0
  5. package/repo-docs/backend/markdown-guide.md +1 -0
  6. package/repo-docs/backend/relations-guide.md +1 -0
  7. package/repo-docs/frontend/form-layout-guide.md +15 -1
  8. package/repo-docs/frontend/markdown-guide.md +1 -0
  9. package/repo-docs/fullstack/edition-collaboration-differences.md +1 -0
  10. package/repo-docs/fullstack/one-to-one-companion-resource-guide.md +447 -0
  11. package/repo-e2e/specs/cabloy-basic.spec.ts +176 -1
  12. package/vona/src/suite/a-training/modules/training-student/package.json +1 -0
  13. package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +177 -103
  14. package/vona/src/suite/a-training/modules/training-student/src/bean/meta.index.ts +1 -0
  15. package/vona/src/suite/a-training/modules/training-student/src/bean/meta.version.ts +19 -1
  16. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +3 -0
  17. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +3 -0
  18. package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +21 -2
  19. package/vona/src/suite/a-training/modules/training-student/src/dto/studentSummary.tsx +7 -1
  20. package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +21 -2
  21. package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +21 -2
  22. package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +0 -3
  23. package/vona/src/suite/a-training/modules/training-student/src/entity/studentContent.tsx +36 -0
  24. package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +6 -0
  25. package/vona/src/suite/a-training/modules/training-student/src/model/studentContent.ts +22 -0
  26. package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +63 -6
  27. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +117 -7
  28. package/zova/packages-zova/zova/package.json +2 -2
  29. package/zova/pnpm-lock.yaml +4 -0
  30. package/zova/src/suite/a-training/modules/training-student/package.json +3 -1
  31. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +40 -40
  32. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +358 -337
  33. package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +18 -7
  34. package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
  35. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +13 -5
  36. package/zova/src/suite-vendor/a-zova/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import type { Locator, Page } from '@playwright/test';
1
+ import type { Locator, Page, Route } from '@playwright/test';
2
2
 
3
3
  import { expect, test } from '@playwright/test';
4
4
 
@@ -56,6 +56,39 @@ function waitForStudentSelect(page: Page) {
56
56
  });
57
57
  }
58
58
 
59
+ function installStudentCreateResponseCapture(page: Page) {
60
+ let studentId: string | number | undefined;
61
+ const handler = async (route: Route) => {
62
+ const request = route.request();
63
+ const url = new URL(request.url());
64
+ if (
65
+ request.method() !== 'POST' ||
66
+ url.pathname !== '/api/training/student' ||
67
+ request.headers()['x-vona-openapi-schema']
68
+ ) {
69
+ await route.continue();
70
+ return;
71
+ }
72
+ const response = await route.fetch();
73
+ const payload = await response.json();
74
+ if (response.ok() && ['string', 'number'].includes(typeof payload.data)) {
75
+ studentId = payload.data;
76
+ }
77
+ await route.fulfill({ response });
78
+ };
79
+ return {
80
+ async install() {
81
+ await page.route('**/api/training/student', handler);
82
+ },
83
+ async uninstall() {
84
+ await page.unroute('**/api/training/student', handler);
85
+ },
86
+ get studentId() {
87
+ return studentId;
88
+ },
89
+ };
90
+ }
91
+
59
92
  async function getFieldGeometry(locator: Locator): Promise<IFieldGeometry> {
60
93
  return locator.evaluate(element => {
61
94
  const field = element.closest('label')?.parentElement;
@@ -348,3 +381,145 @@ test(
348
381
  expect(pageErrors).toEqual([]);
349
382
  },
350
383
  );
384
+
385
+ test(
386
+ 'ATP-BASIC-FORM-01: Training Student content remains in the Basic Information tab',
387
+ { tag: ['@admin', '@flow'] },
388
+ async ({ page }) => {
389
+ const pageErrors = collectPageErrors(page);
390
+ await loginAsAdmin(page);
391
+
392
+ const response = await page.goto('/admin/rest/resource/training-student%3Astudent/create', {
393
+ waitUntil: 'load',
394
+ });
395
+ expect(response?.ok()).toBeTruthy();
396
+ await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
397
+
398
+ const basicInformationTab = page.getByRole('tab', { name: 'Basic Information', exact: true });
399
+ const studentContentGroup = page
400
+ .locator('fieldset')
401
+ .filter({ has: page.locator('legend').filter({ hasText: /^Student Content$/ }) });
402
+ await expect(studentContentGroup).toHaveCount(1);
403
+ await expect(
404
+ studentContentGroup.getByText('Description Markdown', { exact: true }),
405
+ ).toBeVisible();
406
+
407
+ const studentContentPanel = studentContentGroup.locator('xpath=ancestor::*[@role="tabpanel"]');
408
+ await expect(studentContentPanel).toHaveCount(1);
409
+ const basicInformationTabId = await basicInformationTab.getAttribute('id');
410
+ expect(basicInformationTabId).not.toBeNull();
411
+ await expect(studentContentPanel).toHaveAttribute('aria-labelledby', basicInformationTabId!);
412
+ expect(pageErrors).toEqual([]);
413
+ },
414
+ );
415
+
416
+ test(
417
+ 'ATP-BASIC-SUMMARY-01: Training Student summary renders Markdown HTML in a dialog',
418
+ { tag: ['@admin', '@flow'] },
419
+ async ({ page }) => {
420
+ const pageErrors = collectPageErrors(page);
421
+ await loginAsAdmin(page);
422
+
423
+ const studentName = `Summary E2E ${Date.now()}`;
424
+ const createResponseCapture = installStudentCreateResponseCapture(page);
425
+ let studentId: string | number | undefined;
426
+ await createResponseCapture.install();
427
+ try {
428
+ const createPageResponse = await page.goto(
429
+ '/admin/rest/resource/training-student%3Astudent/create',
430
+ { waitUntil: 'load' },
431
+ );
432
+ expect(createPageResponse?.ok()).toBeTruthy();
433
+ await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
434
+ await page
435
+ .getByRole('group', { name: 'Student Name *', exact: true })
436
+ .getByRole('textbox')
437
+ .fill(studentName);
438
+ await page
439
+ .getByRole('group', { name: 'Mobile *', exact: true })
440
+ .getByRole('textbox')
441
+ .fill('13812345678');
442
+ await page
443
+ .locator('.ProseMirror')
444
+ .last()
445
+ .fill(`## Summary heading\n\nSummary paragraph ${studentName}`);
446
+ await page.getByRole('tab', { name: 'Student Training Records', exact: true }).click();
447
+ await page.getByText('Foundation Track', { exact: true }).click();
448
+
449
+ const submitButton = page.getByRole('button', { name: 'Submit', exact: true });
450
+ await expect(submitButton).toBeEnabled();
451
+ await submitButton.click();
452
+ await expect(page).toHaveURL(/\/admin\/(?:\?|$)/);
453
+ studentId = createResponseCapture.studentId;
454
+ expect(['string', 'number']).toContain(typeof studentId);
455
+
456
+ const response = await page.goto('/admin/rest/resource/training-student%3Astudent', {
457
+ waitUntil: 'load',
458
+ });
459
+ expect(response?.ok()).toBeTruthy();
460
+ await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
461
+
462
+ const filterName = page.getByLabel('Student Name');
463
+ await expect(filterName).toBeVisible();
464
+ await filterName.fill(studentName);
465
+ const searchResponse = waitForStudentSelect(page);
466
+ await page.getByRole('button', { name: 'Search', exact: true }).click();
467
+ await searchResponse;
468
+
469
+ const row = page.locator('tr').filter({ hasText: studentName });
470
+ await expect(row).toHaveCount(1);
471
+ const summaryResponse = page.waitForResponse(response => {
472
+ const url = new URL(response.url());
473
+ return (
474
+ response.request().method() === 'GET' &&
475
+ response.ok() &&
476
+ url.pathname === `/api/training/student/summary/${studentId}` &&
477
+ !response.request().headers()['x-vona-openapi-schema']
478
+ );
479
+ });
480
+ await row.getByRole('button', { name: 'Summary', exact: true }).click();
481
+ await summaryResponse;
482
+
483
+ const description = page.locator('.student-summary-description');
484
+ await expect(description).toBeVisible();
485
+ await expect(description.locator('h2')).toHaveText('Summary heading');
486
+ await expect(description).toContainText(`Summary paragraph ${studentName}`);
487
+ expect(pageErrors).toEqual([]);
488
+ } finally {
489
+ try {
490
+ if (studentId !== undefined) {
491
+ const description = page.locator('.student-summary-description');
492
+ if (await description.count()) {
493
+ await description.locator('xpath=../..').getByRole('button').click();
494
+ }
495
+ const row = page.locator('tr').filter({ hasText: studentName });
496
+ if (!(await row.count())) {
497
+ const response = await page.goto('/admin/rest/resource/training-student%3Astudent', {
498
+ waitUntil: 'load',
499
+ });
500
+ expect(response?.ok()).toBeTruthy();
501
+ await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
502
+ await page.getByLabel('Student Name').fill(studentName);
503
+ const searchResponse = waitForStudentSelect(page);
504
+ await page.getByRole('button', { name: 'Search', exact: true }).click();
505
+ await searchResponse;
506
+ }
507
+ const deleteResponse = page.waitForResponse(response => {
508
+ const url = new URL(response.url());
509
+ return (
510
+ response.request().method() === 'DELETE' &&
511
+ response.ok() &&
512
+ url.pathname === `/api/training/student/deleteForce/${studentId}` &&
513
+ !response.request().headers()['x-vona-openapi-schema']
514
+ );
515
+ });
516
+ await row.getByRole('button', { name: 'Force Delete', exact: true }).click();
517
+ await page.getByRole('button', { name: 'Yes', exact: true }).click();
518
+ await deleteResponse;
519
+ }
520
+ } finally {
521
+ await createResponseCapture.uninstall();
522
+ }
523
+ }
524
+ },
525
+ );
@@ -46,6 +46,7 @@
46
46
  "title": "training-student",
47
47
  "vonaModule": {
48
48
  "dependencies": {
49
+ "a-markdown": "5.0.0",
49
50
  "a-vona": "5.0.0"
50
51
  },
51
52
  "fileVersion": 1
@@ -1,15 +1,18 @@
1
1
  // eslint-disable
2
- import type { TypeEntityMeta,TypeModelsClassLikeGeneral,TypeSymbolKeyFieldsMore,IModelRelationHasMany } from 'vona-module-a-orm';
2
+ import type { TypeEntityMeta,TypeModelsClassLikeGeneral,TypeSymbolKeyFieldsMore,IModelRelationHasOne,IModelRelationBelongsTo,IModelRelationHasMany } from 'vona-module-a-orm';
3
3
  import type { TypeEntityOptionsFields,TypeControllerOptionsActions } from 'vona-module-a-openapi';
4
4
  import type { TableIdentity } from 'table-identity';
5
5
  /** entity: begin */
6
6
  export * from '../entity/student.tsx';
7
+ export * from '../entity/studentContent.tsx';
7
8
  import type { IEntityOptionsStudent } from '../entity/student.tsx';
9
+ import type { IEntityOptionsStudentContent } from '../entity/studentContent.tsx';
8
10
  import 'vona-module-a-orm';
9
11
  declare module 'vona-module-a-orm' {
10
12
 
11
13
  export interface IEntityRecord {
12
14
  'training-student:student': IEntityOptionsStudent;
15
+ 'training-student:studentContent': IEntityOptionsStudentContent;
13
16
  }
14
17
 
15
18
 
@@ -20,16 +23,21 @@ declare module 'vona-module-training-student' {
20
23
  /** entity: end */
21
24
  /** entity: begin */
22
25
  import type { EntityStudent } from '../entity/student.tsx';
26
+ import type { EntityStudentContent } from '../entity/studentContent.tsx';
23
27
  export interface IModuleEntity {
24
28
  'student': EntityStudentMeta;
29
+ 'studentContent': EntityStudentContentMeta;
25
30
  }
26
31
  /** entity: end */
27
32
  /** entity: begin */
28
33
  export type EntityStudentTableName = 'trainingStudent';
34
+ export type EntityStudentContentTableName = 'trainingStudentContent';
29
35
  export type EntityStudentMeta=TypeEntityMeta<EntityStudent,EntityStudentTableName>;
36
+ export type EntityStudentContentMeta=TypeEntityMeta<EntityStudentContent,EntityStudentContentTableName>;
30
37
  declare module 'vona-module-a-orm' {
31
38
  export interface ITableRecord {
32
39
  'trainingStudent': EntityStudentMeta;
40
+ 'trainingStudentContent': EntityStudentContentMeta;
33
41
  }
34
42
  }
35
43
  declare module 'vona-module-training-student' {
@@ -37,16 +45,23 @@ declare module 'vona-module-training-student' {
37
45
  export interface IEntityOptionsStudent {
38
46
  fields?: TypeEntityOptionsFields<EntityStudent, IEntityOptionsStudent[TypeSymbolKeyFieldsMore]>;
39
47
  }
48
+
49
+ export interface IEntityOptionsStudentContent {
50
+ fields?: TypeEntityOptionsFields<EntityStudentContent, IEntityOptionsStudentContent[TypeSymbolKeyFieldsMore]>;
51
+ }
40
52
  }
41
53
  /** entity: end */
42
54
  /** model: begin */
43
55
  export * from '../model/student.ts';
56
+ export * from '../model/studentContent.ts';
44
57
  import type { IModelOptionsStudent } from '../model/student.ts';
58
+ import type { IModelOptionsStudentContent } from '../model/studentContent.ts';
45
59
  import 'vona-module-a-orm';
46
60
  declare module 'vona-module-a-orm' {
47
61
 
48
62
  export interface IModelRecord {
49
63
  'training-student:student': IModelOptionsStudent;
64
+ 'training-student:studentContent': IModelOptionsStudentContent;
50
65
  }
51
66
 
52
67
 
@@ -63,12 +78,25 @@ declare module 'vona-module-training-student' {
63
78
  get $onionName(): 'training-student:student';
64
79
  get $onionOptions(): IModelOptionsStudent;
65
80
  }
81
+
82
+ export interface ModelStudentContent {
83
+ /** @internal */
84
+ get scope(): ScopeModuleTrainingStudent;
85
+ }
86
+
87
+ export interface ModelStudentContent {
88
+ get $beanFullName(): 'training-student.model.studentContent';
89
+ get $onionName(): 'training-student:studentContent';
90
+ get $onionOptions(): IModelOptionsStudentContent;
91
+ }
66
92
  }
67
93
  /** model: end */
68
94
  /** model: begin */
69
95
  import type { ModelStudent } from '../model/student.ts';
96
+ import type { ModelStudentContent } from '../model/studentContent.ts';
70
97
  export interface IModuleModel {
71
98
  'student': ModelStudent;
99
+ 'studentContent': ModelStudentContent;
72
100
  }
73
101
  /** model: end */
74
102
  /** model: begin */
@@ -77,6 +105,7 @@ import 'vona';
77
105
  declare module 'vona' {
78
106
  export interface IBeanRecordGeneral {
79
107
  'training-student.model.student': ModelStudent;
108
+ 'training-student.model.studentContent': ModelStudentContent;
80
109
  }
81
110
  }
82
111
  /** model: end */
@@ -87,6 +116,13 @@ declare module 'vona-module-training-student' {
87
116
  export interface IModelOptionsStudent {
88
117
  relations: {
89
118
  trainingRecords: IModelRelationHasMany<'training-record:record', 'studentId', false, 'id'|'name'|'subjectCount'|'totalScore'|'averageScore'|'trainingTime'|'sceneImageIds'|'dossierFileIds'|'description', undefined, undefined, undefined>;
119
+ studentContent: IModelRelationHasOne<'training-student:studentContent', 'studentId', false, 'id'|'studentId'|'descriptionMarkdown'|'descriptionHtml'>;
120
+ studentContentForm: IModelRelationHasOne<'training-student:studentContent', 'studentId', false, 'id'|'studentId'|'descriptionMarkdown'>;
121
+ };
122
+ }
123
+ export interface IModelOptionsStudentContent {
124
+ relations: {
125
+ student: IModelRelationBelongsTo<'training-student:studentContent', 'training-student:student', false, '*'>;
90
126
  };
91
127
  }
92
128
  export interface ModelStudent {
@@ -130,91 +166,51 @@ getByNameEqI<T extends IModelGetOptions<EntityStudent,ModelStudent>>(name?: stri
130
166
  selectByName<T extends IModelSelectParams<EntityStudent,ModelStudent,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(name?: string, params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<TypeModelRelationResult<EntityStudent, ModelStudent, T>[]>;
131
167
  selectByNameEqI<T extends IModelSelectParams<EntityStudent,ModelStudent,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(name?: string, params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<TypeModelRelationResult<EntityStudent, ModelStudent, T>[]>;
132
168
  }
169
+ export interface ModelStudentContent {
170
+ [SymbolKeyEntity]: EntityStudentContent;
171
+ [SymbolKeyEntityMeta]: EntityStudentContentMeta;
172
+ [SymbolKeyModelOptions]: IModelOptionsStudentContent;
173
+ get<T extends IModelGetOptions<EntityStudentContent,ModelStudentContent>>(where: TypeModelWhere<EntityStudentContent>, options?: T): Promise<TypeModelRelationResult<EntityStudentContent, ModelStudentContent, T> | undefined>;
174
+ /**
175
+ * Retrieves one matching primary row with a pessimistic FOR UPDATE lock.
176
+ * Requires an active transaction. The lock is released when that transaction completes.
177
+ * Entity and query caches are bypassed.
178
+ */
179
+ getForUpdate<T extends IModelGetOptions<EntityStudentContent,ModelStudentContent>>(where: TypeModelWhere<EntityStudentContent>, options?: T): Promise<TypeModelRelationResult<EntityStudentContent, ModelStudentContent, T> | undefined>;
180
+ /**
181
+ * Retrieves a primary row by ID with the same pessimistic FOR UPDATE lock semantics.
182
+ * Requires an active transaction. The lock is released when that transaction completes.
183
+ * Entity and query caches are bypassed.
184
+ */
185
+ getByIdForUpdate<T extends IModelGetOptions<EntityStudentContent,ModelStudentContent>>(id: TableIdentity, options?: T): Promise<TypeModelRelationResult<EntityStudentContent, ModelStudentContent, T> | undefined>;
186
+ mget<T extends IModelGetOptions<EntityStudentContent,ModelStudentContent>>(ids: TableIdentity[], options?: T): Promise<TypeModelRelationResult<EntityStudentContent, ModelStudentContent, T>[]>;
187
+ selectAndCount<T extends IModelSelectParams<EntityStudentContent,ModelStudentContent,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<TypeModelSelectAndCount<EntityStudentContent, ModelStudentContent, T>>;
188
+ select<T extends IModelSelectParams<EntityStudentContent,ModelStudentContent,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<TypeModelRelationResult<EntityStudentContent, ModelStudentContent, T>[]>;
189
+ insert<T extends IModelInsertOptions<EntityStudentContent,ModelStudentContent>>(data?: TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T>, options?: T): Promise<TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T, true>>;
190
+ insertBulk<T extends IModelInsertOptions<EntityStudentContent,ModelStudentContent>>(items: TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T>[], options?: T): Promise<TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T, true>[]>;
191
+ update<T extends IModelUpdateOptions<EntityStudentContent,ModelStudentContent>>(data: TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T>, options?: T): Promise<TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T>>;
192
+ updateBulk<T extends IModelUpdateOptions<EntityStudentContent,ModelStudentContent>>(items: TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T>[], options?: T): Promise<TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T>[]>;
193
+ delete<T extends IModelDeleteOptions<EntityStudentContent,ModelStudentContent>>(where?: TypeModelWhere<EntityStudentContent>, options?: T): Promise<void>;
194
+ deleteBulk<T extends IModelDeleteOptions<EntityStudentContent,ModelStudentContent>>(ids: TableIdentity[], options?: T): Promise<void>;
195
+ mutate<T extends IModelMutateOptions<EntityStudentContent,ModelStudentContent>>(data?: TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T>, options?: T): Promise<TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T>>;
196
+ mutateBulk<T extends IModelMutateOptions<EntityStudentContent,ModelStudentContent>>(items: TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T>[], options?: T): Promise<TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T>[]>;
197
+ count<T extends IModelSelectCountParams<EntityStudentContent,ModelStudentContent,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<string | undefined>;
198
+ increment<T extends IModelIncrementParams<EntityStudentContent,ModelStudentContent,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<number>;
199
+ decrement<T extends IModelIncrementParams<EntityStudentContent,ModelStudentContent,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<number>;
200
+ aggregate<T extends IModelSelectAggrParams<EntityStudentContent,ModelStudentContent,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<TypeModelAggrRelationResult<T>>;
201
+ group<T extends IModelSelectGroupParams<EntityStudentContent,ModelStudentContent,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<TypeModelGroupRelationResult<EntityStudentContent, T>[]>;
202
+ getById<T extends IModelGetOptions<EntityStudentContent,ModelStudentContent>>(id: TableIdentity, options?: T): Promise<TypeModelRelationResult<EntityStudentContent, ModelStudentContent, T> | undefined>;
203
+ updateById<T extends IModelUpdateOptions<EntityStudentContent,ModelStudentContent>>(id: TableIdentity, data: TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T>, options?: T): Promise<TypeModelMutateRelationData<EntityStudentContent,ModelStudentContent, T>>;
204
+ deleteById<T extends IModelDeleteOptions<EntityStudentContent,ModelStudentContent>>(id: TableIdentity, options?: T): Promise<void>;
205
+ }
133
206
  }
134
207
  declare module 'vona-module-a-orm' {
135
208
  export interface IModelClassRecord {
136
209
  'training-student:student': ModelStudent;
210
+ 'training-student:studentContent': ModelStudentContent;
137
211
  }
138
212
  }
139
213
  /** model: end */
140
- /** service: begin */
141
- export * from '../service/student.ts';
142
-
143
- import 'vona-module-a-bean';
144
- declare module 'vona-module-a-bean' {
145
-
146
- export interface IServiceRecord {
147
- 'training-student:student': never;
148
- }
149
-
150
-
151
- }
152
- declare module 'vona-module-training-student' {
153
-
154
- export interface ServiceStudent {
155
- /** @internal */
156
- get scope(): ScopeModuleTrainingStudent;
157
- }
158
-
159
- export interface ServiceStudent {
160
- get $beanFullName(): 'training-student.service.student';
161
- get $onionName(): 'training-student:student';
162
- }
163
- }
164
- /** service: end */
165
- /** service: begin */
166
- import type { ServiceStudent } from '../service/student.ts';
167
- export interface IModuleService {
168
- 'student': ServiceStudent;
169
- }
170
- /** service: end */
171
- /** service: begin */
172
-
173
- import 'vona';
174
- declare module 'vona' {
175
- export interface IBeanRecordGeneral {
176
- 'training-student.service.student': ServiceStudent;
177
- }
178
- }
179
- /** service: end */
180
- /** meta: begin */
181
- export * from '../bean/meta.index.ts';
182
- export * from '../bean/meta.version.ts';
183
- import type { IMetaOptionsIndex } from 'vona-module-a-index';
184
- import 'vona-module-a-meta';
185
- declare module 'vona-module-a-meta' {
186
-
187
- export interface IMetaRecord {
188
- 'training-student:index': IMetaOptionsIndex;
189
- 'training-student:version': never;
190
- }
191
-
192
-
193
- }
194
- declare module 'vona-module-training-student' {
195
-
196
- export interface MetaIndex {
197
- /** @internal */
198
- get scope(): ScopeModuleTrainingStudent;
199
- }
200
-
201
- export interface MetaIndex {
202
- get $beanFullName(): 'training-student.meta.index';
203
- get $onionName(): 'training-student:index';
204
- get $onionOptions(): IMetaOptionsIndex;
205
- }
206
-
207
- export interface MetaVersion {
208
- /** @internal */
209
- get scope(): ScopeModuleTrainingStudent;
210
- }
211
-
212
- export interface MetaVersion {
213
- get $beanFullName(): 'training-student.meta.version';
214
- get $onionName(): 'training-student:version';
215
- }
216
- }
217
- /** meta: end */
218
214
  /** dto: begin */
219
215
  export * from '../dto/detailRecordBase.tsx';
220
216
  export * from '../dto/detailRecordMutate.tsx';
@@ -381,58 +377,136 @@ import 'vona-module-a-openapi';
381
377
  }
382
378
 
383
379
  /** controller: end */
384
- /** ssrMenu: begin */
385
- export * from '../bean/ssrMenu.student.ts';
386
- import type { ISsrMenuOptionsStudent } from '../bean/ssrMenu.student.ts';
387
- import 'vona-module-a-ssr';
388
- declare module 'vona-module-a-ssr' {
380
+ /** imageScene: begin */
381
+ export * from '../bean/imageScene.studentImage.ts';
389
382
 
390
- export interface ISsrMenuRecord {
391
- 'training-student:student': ISsrMenuOptionsStudent;
383
+ import { type IDecoratorImageSceneOptions } from 'vona-module-a-image';
384
+ declare module 'vona-module-a-image' {
385
+
386
+ export interface IImageSceneRecord {
387
+ 'training-student:studentImage': IDecoratorImageSceneOptions;
392
388
  }
393
389
 
394
390
 
395
391
  }
396
392
  declare module 'vona-module-training-student' {
397
393
 
398
- export interface SsrMenuStudent {
394
+ export interface ImageSceneStudentImage {
399
395
  /** @internal */
400
396
  get scope(): ScopeModuleTrainingStudent;
401
397
  }
402
398
 
403
- export interface SsrMenuStudent {
404
- get $beanFullName(): 'training-student.ssrMenu.student';
399
+ export interface ImageSceneStudentImage {
400
+ get $beanFullName(): 'training-student.imageScene.studentImage';
401
+ get $onionName(): 'training-student:studentImage';
402
+ get $onionOptions(): IDecoratorImageSceneOptions;
403
+ }
404
+ }
405
+ /** imageScene: end */
406
+ /** service: begin */
407
+ export * from '../service/student.ts';
408
+
409
+ import 'vona-module-a-bean';
410
+ declare module 'vona-module-a-bean' {
411
+
412
+ export interface IServiceRecord {
413
+ 'training-student:student': never;
414
+ }
415
+
416
+
417
+ }
418
+ declare module 'vona-module-training-student' {
419
+
420
+ export interface ServiceStudent {
421
+ /** @internal */
422
+ get scope(): ScopeModuleTrainingStudent;
423
+ }
424
+
425
+ export interface ServiceStudent {
426
+ get $beanFullName(): 'training-student.service.student';
405
427
  get $onionName(): 'training-student:student';
406
- get $onionOptions(): ISsrMenuOptionsStudent;
407
428
  }
408
429
  }
409
- /** ssrMenu: end */
410
- /** imageScene: begin */
411
- export * from '../bean/imageScene.studentImage.ts';
430
+ /** service: end */
431
+ /** service: begin */
432
+ import type { ServiceStudent } from '../service/student.ts';
433
+ export interface IModuleService {
434
+ 'student': ServiceStudent;
435
+ }
436
+ /** service: end */
437
+ /** service: begin */
412
438
 
413
- import { type IDecoratorImageSceneOptions } from 'vona-module-a-image';
414
- declare module 'vona-module-a-image' {
439
+ import 'vona';
440
+ declare module 'vona' {
441
+ export interface IBeanRecordGeneral {
442
+ 'training-student.service.student': ServiceStudent;
443
+ }
444
+ }
445
+ /** service: end */
446
+ /** meta: begin */
447
+ export * from '../bean/meta.index.ts';
448
+ export * from '../bean/meta.version.ts';
449
+ import type { IMetaOptionsIndex } from 'vona-module-a-index';
450
+ import 'vona-module-a-meta';
451
+ declare module 'vona-module-a-meta' {
415
452
 
416
- export interface IImageSceneRecord {
417
- 'training-student:studentImage': IDecoratorImageSceneOptions;
453
+ export interface IMetaRecord {
454
+ 'training-student:index': IMetaOptionsIndex;
455
+ 'training-student:version': never;
418
456
  }
419
457
 
420
458
 
421
459
  }
422
460
  declare module 'vona-module-training-student' {
423
461
 
424
- export interface ImageSceneStudentImage {
462
+ export interface MetaIndex {
425
463
  /** @internal */
426
464
  get scope(): ScopeModuleTrainingStudent;
427
465
  }
428
466
 
429
- export interface ImageSceneStudentImage {
430
- get $beanFullName(): 'training-student.imageScene.studentImage';
431
- get $onionName(): 'training-student:studentImage';
432
- get $onionOptions(): IDecoratorImageSceneOptions;
467
+ export interface MetaIndex {
468
+ get $beanFullName(): 'training-student.meta.index';
469
+ get $onionName(): 'training-student:index';
470
+ get $onionOptions(): IMetaOptionsIndex;
471
+ }
472
+
473
+ export interface MetaVersion {
474
+ /** @internal */
475
+ get scope(): ScopeModuleTrainingStudent;
476
+ }
477
+
478
+ export interface MetaVersion {
479
+ get $beanFullName(): 'training-student.meta.version';
480
+ get $onionName(): 'training-student:version';
433
481
  }
434
482
  }
435
- /** imageScene: end */
483
+ /** meta: end */
484
+ /** ssrMenu: begin */
485
+ export * from '../bean/ssrMenu.student.ts';
486
+ import type { ISsrMenuOptionsStudent } from '../bean/ssrMenu.student.ts';
487
+ import 'vona-module-a-ssr';
488
+ declare module 'vona-module-a-ssr' {
489
+
490
+ export interface ISsrMenuRecord {
491
+ 'training-student:student': ISsrMenuOptionsStudent;
492
+ }
493
+
494
+
495
+ }
496
+ declare module 'vona-module-training-student' {
497
+
498
+ export interface SsrMenuStudent {
499
+ /** @internal */
500
+ get scope(): ScopeModuleTrainingStudent;
501
+ }
502
+
503
+ export interface SsrMenuStudent {
504
+ get $beanFullName(): 'training-student.ssrMenu.student';
505
+ get $onionName(): 'training-student:student';
506
+ get $onionOptions(): ISsrMenuOptionsStudent;
507
+ }
508
+ }
509
+ /** ssrMenu: end */
436
510
  /** locale: begin */
437
511
  import { locales } from './locales.ts';
438
512
  /** locale: end */
@@ -7,6 +7,7 @@ import { $tableColumns } from 'vona-module-a-ormutils';
7
7
  @Meta<IMetaOptionsIndex>({
8
8
  indexes: {
9
9
  ...$tableColumns('trainingStudent', 'name'),
10
+ ...$tableColumns('trainingStudentContent', 'studentId'),
10
11
  },
11
12
  })
12
13
  export class MetaIndex extends BeanBase {}
@@ -12,11 +12,29 @@ export class MetaVersion extends BeanBase implements IMetaVersionUpdate {
12
12
  table.comment(entityStudent.$comment.$table);
13
13
  table.basicFields();
14
14
  table.string(entityStudent.name, 50).comment(entityStudent.$comment.name);
15
- table.string(entityStudent.description, 255).comment(entityStudent.$comment.description);
16
15
  table.string(entityStudent.mobile, 50).comment(entityStudent.$comment.mobile);
17
16
  table.tableIdentity(entityStudent.imageId).comment(entityStudent.$comment.imageId);
18
17
  table.integer(entityStudent.level).comment(entityStudent.$comment.level);
19
18
  });
19
+
20
+ const entityStudentContent = this.scope.entity.studentContent;
21
+ await this.bean.model.createTable(entityStudentContent.$table, table => {
22
+ table.comment(entityStudentContent.$comment.$table);
23
+ table.basicFields();
24
+ table
25
+ .text(entityStudentContent.descriptionMarkdown)
26
+ .comment(entityStudentContent.$comment.descriptionMarkdown);
27
+ table
28
+ .text(entityStudentContent.descriptionHtml)
29
+ .comment(entityStudentContent.$comment.descriptionHtml);
30
+ table
31
+ .tableIdentity(entityStudentContent.studentId)
32
+ .comment(entityStudentContent.$comment.studentId);
33
+ table.index(
34
+ [entityStudentContent.studentId],
35
+ `idx_${entityStudentContent.$table}_studentId`,
36
+ );
37
+ });
20
38
  }
21
39
  }
22
40
  }
@@ -1,5 +1,8 @@
1
1
  export default {
2
2
  Description: 'Description',
3
+ DescriptionMarkdown: 'Description Markdown',
4
+ DescriptionHtml: 'Description HTML',
5
+ StudentContent: 'Student Content',
3
6
  Level: 'Training Stage',
4
7
  BasicInformation: 'Basic Information',
5
8
  StudentProfile: 'Student Profile',
@@ -1,5 +1,8 @@
1
1
  export default {
2
2
  Description: '描述',
3
+ DescriptionMarkdown: '描述 Markdown',
4
+ DescriptionHtml: '描述 HTML',
5
+ StudentContent: '学生内容',
3
6
  Level: '培训阶段',
4
7
  BasicInformation: '基本信息',
5
8
  StudentProfile: '学生资料',