cabloy 5.1.148 → 5.1.150
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/.cabloy-version +1 -1
- package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +2 -1
- package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +1 -0
- package/CHANGELOG.md +33 -0
- package/CLAUDE.md +1 -0
- package/package.json +1 -1
- package/repo-docs/.vitepress/config.mjs +4 -0
- package/repo-docs/backend/dto-guide.md +7 -6
- package/repo-docs/backend/dto-infer-generation.md +25 -5
- package/repo-docs/backend/markdown-guide.md +1 -0
- package/repo-docs/backend/relations-guide.md +1 -0
- package/repo-docs/frontend/a-model-under-the-hood.md +38 -0
- package/repo-docs/frontend/a-openapi-under-the-hood.md +13 -2
- package/repo-docs/frontend/form-layout-guide.md +39 -8
- package/repo-docs/frontend/markdown-guide.md +1 -0
- package/repo-docs/frontend/model-resource-best-practices.md +2 -0
- package/repo-docs/frontend/model-resource-cookbook.md +7 -1
- package/repo-docs/frontend/use-state-data-best-practices.md +44 -0
- package/repo-docs/frontend/zova-form-source-reading-map.md +26 -9
- package/repo-docs/frontend/zova-form-under-the-hood.md +11 -0
- package/repo-docs/fullstack/edition-collaboration-differences.md +1 -0
- package/repo-docs/fullstack/one-to-one-companion-resource-guide.md +447 -0
- package/repo-e2e/specs/a-commerce.spec.ts +40 -0
- package/repo-e2e/specs/cabloy-basic.spec.ts +176 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/controller/{{resourceName}}.ts_ +2 -2
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/pnpm-lock.yaml +83 -75
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +108 -108
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuSelectResItem.tsx +12 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/sku.ts +19 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/skuPresentation.test.ts +53 -4
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +148 -148
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAdjust.tsx +37 -3
- package/vona/src/suite/a-training/modules/training-student/package.json +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +177 -103
- package/vona/src/suite/a-training/modules/training-student/src/bean/meta.index.ts +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/bean/meta.version.ts +19 -1
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +3 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +3 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +22 -2
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSummary.tsx +7 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +22 -2
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +25 -3
- package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +0 -3
- package/vona/src/suite/a-training/modules/training-student/src/entity/studentContent.tsx +36 -0
- package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +6 -0
- package/vona/src/suite/a-training/modules/training-student/src/model/studentContent.ts +22 -0
- package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +43 -14
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +137 -8
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +6 -2
- package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/package.json +6 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/component/tableCellActionAdjustStock.ts +34 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +81 -6
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeStockBalance.ts +32 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +44 -44
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +356 -338
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeStockBalance.ts +17 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/bean/tableCell.actionAdjustStock.tsx +35 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionAdjustStock/controller.tsx +138 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +3 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +3 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/stockBalance.ts +36 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +4 -4
- package/zova/src/suite/a-training/modules/training-student/package.json +3 -1
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +44 -44
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +360 -339
- package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +19 -8
- package/zova/src/suite/cabloy-basic/modules/basic-app/src/bean/behavior.appModal.tsx +2 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +2 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/test/model/resource.types.ts +11 -0
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +69 -12
- package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/formLayout.test.ts +157 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.ts +3 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts +11 -13
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useState.ts +9 -7
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useStateGeneral.ts +5 -12
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/queryPersister.ts +17 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/queryRefetch.ts +34 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +27 -3
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/queryPersister.test.ts +115 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/queryRefetch.test.ts +84 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/lib/schema.ts +27 -5
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts +4 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/schema.ts +2 -5
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/test/lib/schema.test.ts +179 -0
- package/zova/src/suite-vendor/a-zova/package.json +4 -4
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { IDecoratorDtoOptions } from 'vona-module-a-web';
|
|
2
2
|
|
|
3
3
|
import { DtoImageView } from 'vona-module-a-image';
|
|
4
|
-
import { $makeMetadata, Api, v } from 'vona-module-a-openapiutils';
|
|
4
|
+
import { $makeMetadata, $makeSchema, Api, v } from 'vona-module-a-openapiutils';
|
|
5
5
|
import { $Dto } from 'vona-module-a-orm';
|
|
6
6
|
import { Dto } from 'vona-module-a-web';
|
|
7
|
+
import z from 'zod';
|
|
7
8
|
import { ZovaRender } from 'zova-rest-cabloy-basic-admin';
|
|
8
9
|
|
|
9
10
|
import { $locale } from '../.metadata/locales.ts';
|
|
@@ -11,7 +12,14 @@ import { ModelStudent } from '../model/student.ts';
|
|
|
11
12
|
import { DtoDetailRecordResItem } from './detailRecordResItem.tsx';
|
|
12
13
|
import { DtoDetailRecordView } from './detailRecordView.tsx';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
const studentContentField = $makeSchema(
|
|
16
|
+
ZovaRender.fieldSource('content.descriptionMarkdown'),
|
|
17
|
+
ZovaRender.field('basic-markdown:formFieldMarkdown'),
|
|
18
|
+
v.optional(),
|
|
19
|
+
z.string(),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export interface IDtoOptionsStudentView extends IDecoratorDtoOptions<'_descriptionMarkdown'> {}
|
|
15
23
|
|
|
16
24
|
@Dto<IDtoOptionsStudentView>({
|
|
17
25
|
blocks: [
|
|
@@ -44,6 +52,16 @@ export interface IDtoOptionsStudentView extends IDecoratorDtoOptions {}
|
|
|
44
52
|
},
|
|
45
53
|
],
|
|
46
54
|
},
|
|
55
|
+
{
|
|
56
|
+
type: 'group',
|
|
57
|
+
title: $locale('StudentContent'),
|
|
58
|
+
children: [
|
|
59
|
+
{
|
|
60
|
+
type: 'section',
|
|
61
|
+
children: [{ type: 'field', name: '_descriptionMarkdown' }],
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
47
65
|
],
|
|
48
66
|
},
|
|
49
67
|
{
|
|
@@ -73,6 +91,7 @@ export interface IDtoOptionsStudentView extends IDecoratorDtoOptions {}
|
|
|
73
91
|
}),
|
|
74
92
|
],
|
|
75
93
|
fields: {
|
|
94
|
+
_descriptionMarkdown: studentContentField,
|
|
76
95
|
trainingRecords: $makeMetadata(
|
|
77
96
|
v.title($locale('TrainingRecords')),
|
|
78
97
|
ZovaRender.order(6),
|
|
@@ -81,7 +100,10 @@ export interface IDtoOptionsStudentView extends IDecoratorDtoOptions {}
|
|
|
81
100
|
},
|
|
82
101
|
})
|
|
83
102
|
export class DtoStudentView extends $Dto.get(() => ModelStudent, {
|
|
84
|
-
include: {
|
|
103
|
+
include: {
|
|
104
|
+
content: { columns: ['descriptionMarkdown'] },
|
|
105
|
+
trainingRecords: { dtoClass: DtoDetailRecordView },
|
|
106
|
+
},
|
|
85
107
|
}) {
|
|
86
108
|
@Api.field(
|
|
87
109
|
ZovaRender.visible(false),
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { TableIdentity } from 'table-identity';
|
|
2
|
+
import type { IDecoratorEntityOptions } from 'vona-module-a-orm';
|
|
3
|
+
|
|
4
|
+
import { $makeMetadata, Api, v } from 'vona-module-a-openapiutils';
|
|
5
|
+
import { Entity, EntityBase } from 'vona-module-a-orm';
|
|
6
|
+
import { ZovaRender } from 'zova-rest-cabloy-basic-admin';
|
|
7
|
+
|
|
8
|
+
import { $locale } from '../.metadata/locales.ts';
|
|
9
|
+
|
|
10
|
+
export interface IEntityOptionsStudentContent extends IDecoratorEntityOptions {}
|
|
11
|
+
|
|
12
|
+
@Entity<IEntityOptionsStudentContent>('trainingStudentContent', {
|
|
13
|
+
openapi: { title: $locale('StudentContent') },
|
|
14
|
+
fields: {
|
|
15
|
+
id: $makeMetadata(ZovaRender.visible(false)),
|
|
16
|
+
iid: $makeMetadata(ZovaRender.visible(false)),
|
|
17
|
+
deleted: $makeMetadata(ZovaRender.visible(false)),
|
|
18
|
+
createdAt: $makeMetadata(ZovaRender.visible(false)),
|
|
19
|
+
updatedAt: $makeMetadata(ZovaRender.visible(false)),
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
export class EntityStudentContent extends EntityBase {
|
|
23
|
+
@Api.field(v.tableIdentity(), ZovaRender.visible(false))
|
|
24
|
+
studentId: TableIdentity;
|
|
25
|
+
|
|
26
|
+
@Api.field(
|
|
27
|
+
v.title($locale('DescriptionMarkdown')),
|
|
28
|
+
v.optional(),
|
|
29
|
+
ZovaRender.order(1),
|
|
30
|
+
ZovaRender.field('basic-markdown:formFieldMarkdown'),
|
|
31
|
+
)
|
|
32
|
+
descriptionMarkdown?: string;
|
|
33
|
+
|
|
34
|
+
@Api.field(v.title($locale('DescriptionHtml')), v.optional(), ZovaRender.visible(false))
|
|
35
|
+
descriptionHtml?: string;
|
|
36
|
+
}
|
|
@@ -22,6 +22,12 @@ export interface IModelOptionsStudent extends IDecoratorModelOptions<EntityStude
|
|
|
22
22
|
'description',
|
|
23
23
|
],
|
|
24
24
|
}),
|
|
25
|
+
content: $relation.hasOne('training-student:studentContent', 'studentId', {
|
|
26
|
+
columns: ['id', 'descriptionMarkdown'],
|
|
27
|
+
}),
|
|
28
|
+
contentHtml: $relation.hasOne('training-student:studentContent', 'studentId', {
|
|
29
|
+
columns: ['id', 'descriptionHtml'],
|
|
30
|
+
}),
|
|
25
31
|
},
|
|
26
32
|
})
|
|
27
33
|
export class ModelStudent extends BeanModelBase<EntityStudent> {}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { IDecoratorModelOptions } from 'vona-module-a-orm';
|
|
2
|
+
|
|
3
|
+
import { $relation, BeanModelBase, Model } from 'vona-module-a-orm';
|
|
4
|
+
|
|
5
|
+
import { EntityStudentContent } from '../entity/studentContent.tsx';
|
|
6
|
+
|
|
7
|
+
export interface IModelOptionsStudentContent extends IDecoratorModelOptions<EntityStudentContent> {}
|
|
8
|
+
|
|
9
|
+
@Model<IModelOptionsStudentContent>({
|
|
10
|
+
entity: EntityStudentContent,
|
|
11
|
+
relations: {
|
|
12
|
+
student: $relation.belongsTo(
|
|
13
|
+
'training-student:studentContent',
|
|
14
|
+
'training-student:student',
|
|
15
|
+
'studentId',
|
|
16
|
+
),
|
|
17
|
+
},
|
|
18
|
+
cache: {
|
|
19
|
+
modelsClear: 'training-student:student',
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
export class ModelStudentContent extends BeanModelBase<EntityStudentContent> {}
|
|
@@ -12,18 +12,30 @@ import type { DtoStudentView } from '../dto/studentView.tsx';
|
|
|
12
12
|
import type { EntityStudent } from '../entity/student.tsx';
|
|
13
13
|
import type { ModelStudent } from '../model/student.ts';
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
type StudentContentInput = {
|
|
16
|
+
descriptionMarkdown?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function getStudentRelationsInclude(): {
|
|
20
|
+
content: true;
|
|
16
21
|
trainingRecords: { include: { trainingRecordSubjects: true } };
|
|
17
22
|
} {
|
|
18
|
-
return {
|
|
23
|
+
return {
|
|
24
|
+
content: true,
|
|
25
|
+
trainingRecords: { include: { trainingRecordSubjects: true } },
|
|
26
|
+
};
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
@Service()
|
|
22
30
|
export class ServiceStudent extends BeanBase {
|
|
23
31
|
async create(student: DtoStudentCreate): Promise<EntityStudent> {
|
|
24
|
-
return await this.scope.model.student.insert(
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
return await this.scope.model.student.insert(
|
|
33
|
+
{
|
|
34
|
+
...student,
|
|
35
|
+
content: this._prepareContent(student.content),
|
|
36
|
+
},
|
|
37
|
+
{ include: getStudentRelationsInclude() },
|
|
38
|
+
);
|
|
27
39
|
}
|
|
28
40
|
|
|
29
41
|
async select(params?: IQueryParams<ModelStudent>): Promise<DtoStudentSelectRes> {
|
|
@@ -32,20 +44,28 @@ export class ServiceStudent extends BeanBase {
|
|
|
32
44
|
|
|
33
45
|
async view(id: TableIdentity): Promise<DtoStudentView | undefined> {
|
|
34
46
|
return await this.scope.model.student.getById(id, {
|
|
35
|
-
include:
|
|
47
|
+
include: getStudentRelationsInclude(),
|
|
36
48
|
});
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
async update(id: TableIdentity, student: DtoStudentUpdate) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
const content = Object.hasOwn(student, 'content')
|
|
53
|
+
? this._prepareContent(student.content)
|
|
54
|
+
: undefined;
|
|
55
|
+
return await this.scope.model.student.updateById(
|
|
56
|
+
id,
|
|
57
|
+
content === undefined ? student : { ...student, content },
|
|
58
|
+
{ include: getStudentRelationsInclude() },
|
|
59
|
+
);
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
async summary(id: TableIdentity): Promise<DtoStudentSummary | undefined> {
|
|
46
|
-
const student = await this.scope.model.student.getById(id
|
|
63
|
+
const student = await this.scope.model.student.getById(id, {
|
|
64
|
+
include: { content: true, contentHtml: true },
|
|
65
|
+
});
|
|
47
66
|
if (!student) return undefined;
|
|
48
|
-
const
|
|
67
|
+
const descriptionMarkdown = student.content?.descriptionMarkdown;
|
|
68
|
+
const descriptionLength = descriptionMarkdown?.length ?? 0;
|
|
49
69
|
const levelTitle = String(student.level);
|
|
50
70
|
return {
|
|
51
71
|
id: student.id,
|
|
@@ -53,7 +73,8 @@ export class ServiceStudent extends BeanBase {
|
|
|
53
73
|
mobile: student.mobile,
|
|
54
74
|
level: student.level,
|
|
55
75
|
levelTitle,
|
|
56
|
-
|
|
76
|
+
descriptionMarkdown,
|
|
77
|
+
descriptionHtml: student.contentHtml?.descriptionHtml,
|
|
57
78
|
descriptionLength,
|
|
58
79
|
summaryText: `${student.name} is in level ${student.level}. Description length: ${descriptionLength}.`,
|
|
59
80
|
};
|
|
@@ -61,14 +82,22 @@ export class ServiceStudent extends BeanBase {
|
|
|
61
82
|
|
|
62
83
|
async delete(id: TableIdentity) {
|
|
63
84
|
return await this.scope.model.student.deleteById(id, {
|
|
64
|
-
include:
|
|
85
|
+
include: getStudentRelationsInclude(),
|
|
65
86
|
});
|
|
66
87
|
}
|
|
67
88
|
|
|
68
89
|
async deleteForce(id: TableIdentity) {
|
|
69
90
|
return await this.scope.model.student.deleteById(id, {
|
|
70
91
|
disableDeleted: true,
|
|
71
|
-
include:
|
|
92
|
+
include: getStudentRelationsInclude(),
|
|
72
93
|
});
|
|
73
94
|
}
|
|
95
|
+
|
|
96
|
+
private _prepareContent(content: StudentContentInput | undefined) {
|
|
97
|
+
const descriptionMarkdown = content?.descriptionMarkdown ?? '';
|
|
98
|
+
return {
|
|
99
|
+
descriptionMarkdown,
|
|
100
|
+
descriptionHtml: this.bean.markdown.renderHtml(descriptionMarkdown),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
74
103
|
}
|
|
@@ -25,17 +25,36 @@ describe('student.test.ts', () => {
|
|
|
25
25
|
const formLayout =
|
|
26
26
|
blocks?.[0]?.options?.blocks?.[0]?.options?.blocks?.[0]?.options?.formLayout;
|
|
27
27
|
const tabs = formLayout?.children[0];
|
|
28
|
-
const
|
|
28
|
+
const groups = tabs?.children[0]?.children;
|
|
29
|
+
const profileSection = groups?.[0]?.children[0];
|
|
30
|
+
const studentContentSection = groups?.[1]?.children[0];
|
|
29
31
|
const trainingRecordsSection = tabs?.children[1]?.children[1];
|
|
30
32
|
assert.equal(tabs?.type, 'tabs');
|
|
31
33
|
assert.equal(tabs?.id, undefined);
|
|
32
34
|
assert.equal(tabs?.children[1]?.type, 'tab');
|
|
33
35
|
assert.equal(tabs?.children[1]?.id, undefined);
|
|
36
|
+
assert.equal(groups?.length, 2);
|
|
37
|
+
assert.deepEqual(
|
|
38
|
+
groups?.map(item => item.type),
|
|
39
|
+
['group', 'group'],
|
|
40
|
+
);
|
|
34
41
|
assert.deepEqual(profileSection?.columns, { default: 1, md: 2 });
|
|
35
42
|
assert.deepEqual(
|
|
36
43
|
profileSection?.children.map(item => item.name),
|
|
37
44
|
['name', 'mobile', 'imageId'],
|
|
38
45
|
);
|
|
46
|
+
const descriptionFieldName =
|
|
47
|
+
DtoClass === DtoStudentView ? '_descriptionMarkdown' : 'content';
|
|
48
|
+
assert.deepEqual(
|
|
49
|
+
studentContentSection?.children.map(item => item.name),
|
|
50
|
+
[descriptionFieldName],
|
|
51
|
+
);
|
|
52
|
+
const descriptionField = (component as any).properties[descriptionFieldName];
|
|
53
|
+
assert.equal(descriptionField.rest.fieldSource, 'content.descriptionMarkdown');
|
|
54
|
+
assert.equal(descriptionField.rest.form.render, 'basic-markdown:formFieldMarkdown');
|
|
55
|
+
if (DtoClass === DtoStudentView) {
|
|
56
|
+
assert.equal((component as any).required?.includes('_descriptionMarkdown'), false);
|
|
57
|
+
}
|
|
39
58
|
assert.equal(tabs?.children[1]?.children[0]?.name, 'level');
|
|
40
59
|
assert.deepEqual(
|
|
41
60
|
trainingRecordsSection?.children.map(item => item.name),
|
|
@@ -92,7 +111,8 @@ describe('student.test.ts', () => {
|
|
|
92
111
|
'name',
|
|
93
112
|
'mobile',
|
|
94
113
|
'level',
|
|
95
|
-
'
|
|
114
|
+
'descriptionMarkdown',
|
|
115
|
+
'descriptionHtml',
|
|
96
116
|
'levelTitle',
|
|
97
117
|
'descriptionLength',
|
|
98
118
|
'summaryText',
|
|
@@ -118,9 +138,13 @@ describe('student.test.ts', () => {
|
|
|
118
138
|
const maskedMobileUpdate = '139****4321';
|
|
119
139
|
const trainingTime = new Date('2026-03-10T08:00:00.000Z');
|
|
120
140
|
const trainingTimeUpdate = new Date('2026-04-18T13:20:00.000Z');
|
|
141
|
+
const description =
|
|
142
|
+
`# Student profile\n\n${'This is a long Markdown description. '.repeat(10)}`.trim();
|
|
143
|
+
const descriptionUpdate =
|
|
144
|
+
`## Updated profile\n\n${'This is updated Markdown content. '.repeat(10)}`.trim();
|
|
121
145
|
const data = {
|
|
122
146
|
name: '__Tom__',
|
|
123
|
-
|
|
147
|
+
content: { descriptionMarkdown: description },
|
|
124
148
|
mobile,
|
|
125
149
|
level: 1,
|
|
126
150
|
trainingRecords: [
|
|
@@ -157,6 +181,7 @@ describe('student.test.ts', () => {
|
|
|
157
181
|
assert.equal(!!studentItem, true);
|
|
158
182
|
assert.equal(studentItem!.level, data.level);
|
|
159
183
|
assert.equal(studentItem!.mobile, maskedMobile);
|
|
184
|
+
assert.equal((studentItem as any).description, undefined);
|
|
160
185
|
// findMany: level filter
|
|
161
186
|
const selectResByLevel: DtoStudentSelectRes = await app.bean.executor.performAction(
|
|
162
187
|
'get',
|
|
@@ -182,6 +207,7 @@ describe('student.test.ts', () => {
|
|
|
182
207
|
const record = student.trainingRecords?.[0];
|
|
183
208
|
const recordSubject = record?.trainingRecordSubjects?.[0];
|
|
184
209
|
assert.equal(student.trainingRecords?.length, 1);
|
|
210
|
+
assert.equal(student.content?.descriptionMarkdown, description);
|
|
185
211
|
assert.equal(record?.name, '__Record__');
|
|
186
212
|
assert.equal(record?.subjectCount, 1);
|
|
187
213
|
assert.equal(record?.totalScore, 88);
|
|
@@ -193,7 +219,7 @@ describe('student.test.ts', () => {
|
|
|
193
219
|
// update
|
|
194
220
|
const dataUpdate = {
|
|
195
221
|
name: '__TomNew__',
|
|
196
|
-
|
|
222
|
+
content: { descriptionMarkdown: descriptionUpdate },
|
|
197
223
|
mobile: mobileUpdate,
|
|
198
224
|
level: 2,
|
|
199
225
|
trainingRecords: [
|
|
@@ -236,6 +262,7 @@ describe('student.test.ts', () => {
|
|
|
236
262
|
assert.equal(student.name, dataUpdate.name);
|
|
237
263
|
assert.equal(student.level, dataUpdate.level);
|
|
238
264
|
assert.equal(student.mobile, maskedMobileUpdate);
|
|
265
|
+
assert.equal(student.content?.descriptionMarkdown, descriptionUpdate);
|
|
239
266
|
assert.equal(student.trainingRecords?.length, 1);
|
|
240
267
|
assert.equal(updatedRecord?.name, '__RecordNew__');
|
|
241
268
|
assert.equal(updatedRecord?.subjectCount, 2);
|
|
@@ -254,6 +281,15 @@ describe('student.test.ts', () => {
|
|
|
254
281
|
disableDeleted: true,
|
|
255
282
|
});
|
|
256
283
|
assert.equal(studentRaw!.mobile, mobileUpdate);
|
|
284
|
+
assert.equal((studentRaw as any).description, undefined);
|
|
285
|
+
const studentContent = await app
|
|
286
|
+
.scope('training-student')
|
|
287
|
+
.model.studentContent.get({ studentId });
|
|
288
|
+
assert.equal(studentContent?.descriptionMarkdown, descriptionUpdate);
|
|
289
|
+
assert.equal(
|
|
290
|
+
studentContent?.descriptionHtml,
|
|
291
|
+
app.bean.markdown.renderHtml(descriptionUpdate),
|
|
292
|
+
);
|
|
257
293
|
// summary
|
|
258
294
|
const summary: DtoStudentSummary = await app.bean.executor.performAction(
|
|
259
295
|
'get',
|
|
@@ -263,7 +299,9 @@ describe('student.test.ts', () => {
|
|
|
263
299
|
assert.equal(summary.name, dataUpdate.name);
|
|
264
300
|
assert.equal(summary.mobile, maskedMobileUpdate);
|
|
265
301
|
assert.equal(summary.level, dataUpdate.level);
|
|
266
|
-
assert.equal(summary.
|
|
302
|
+
assert.equal(summary.descriptionMarkdown, descriptionUpdate);
|
|
303
|
+
assert.equal(summary.descriptionHtml, app.bean.markdown.renderHtml(descriptionUpdate));
|
|
304
|
+
assert.equal(summary.descriptionLength, descriptionUpdate.length);
|
|
267
305
|
assert.equal(typeof summary.levelTitle, 'string');
|
|
268
306
|
assert.equal(typeof summary.summaryText, 'string');
|
|
269
307
|
// delete
|
|
@@ -271,6 +309,14 @@ describe('student.test.ts', () => {
|
|
|
271
309
|
params: { id: student.id },
|
|
272
310
|
});
|
|
273
311
|
assert.equal(deleteRes, null);
|
|
312
|
+
const studentContentDeleted = await app
|
|
313
|
+
.scope('training-student')
|
|
314
|
+
.model.studentContent.get({ studentId }, { disableDeleted: true });
|
|
315
|
+
assert.equal(studentContentDeleted?.deleted, true);
|
|
316
|
+
assert.equal(
|
|
317
|
+
await app.scope('training-student').model.studentContent.get({ studentId }),
|
|
318
|
+
undefined,
|
|
319
|
+
);
|
|
274
320
|
// findOne
|
|
275
321
|
student = await app.bean.executor.performAction('get', '/training/student/:id', {
|
|
276
322
|
params: { id: student.id },
|
|
@@ -294,11 +340,94 @@ describe('student.test.ts', () => {
|
|
|
294
340
|
disableDeleted: true,
|
|
295
341
|
});
|
|
296
342
|
assert.equal(studentForce, undefined);
|
|
343
|
+
assert.equal(
|
|
344
|
+
await app
|
|
345
|
+
.scope('training-student')
|
|
346
|
+
.model.studentContent.get({ studentId: studentIdForce }, { disableDeleted: true }),
|
|
347
|
+
undefined,
|
|
348
|
+
);
|
|
297
349
|
// logout
|
|
298
350
|
await app.bean.passport.signout();
|
|
299
351
|
});
|
|
300
352
|
});
|
|
301
353
|
|
|
354
|
+
it('action:student:contentPersistence', async () => {
|
|
355
|
+
await app.bean.executor.mockCtx(async () => {
|
|
356
|
+
await app.bean.passport.signinMock();
|
|
357
|
+
let studentId: string | undefined;
|
|
358
|
+
try {
|
|
359
|
+
studentId = await app.bean.executor.performAction('post', '/training/student', {
|
|
360
|
+
body: {
|
|
361
|
+
name: '__ContentStudent__',
|
|
362
|
+
mobile: '13812345678',
|
|
363
|
+
level: 1,
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
const studentContentModel = app.scope('training-student').model.studentContent;
|
|
367
|
+
const createdContent = await studentContentModel.get({ studentId });
|
|
368
|
+
assert.equal(createdContent?.descriptionMarkdown, '');
|
|
369
|
+
assert.equal(createdContent?.descriptionHtml, '');
|
|
370
|
+
|
|
371
|
+
await app.bean.executor.performAction('patch', '/training/student/:id', {
|
|
372
|
+
params: { id: studentId },
|
|
373
|
+
body: {
|
|
374
|
+
name: '__ContentStudentUpdated__',
|
|
375
|
+
mobile: '13812345678',
|
|
376
|
+
level: 1,
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
const preservedContent = await studentContentModel.get({ studentId });
|
|
380
|
+
assert.equal(preservedContent?.descriptionMarkdown, '');
|
|
381
|
+
assert.equal(preservedContent?.descriptionHtml, '');
|
|
382
|
+
|
|
383
|
+
const markdown = ' ## Created later ';
|
|
384
|
+
await app.bean.executor.performAction('patch', '/training/student/:id', {
|
|
385
|
+
params: { id: studentId },
|
|
386
|
+
body: {
|
|
387
|
+
name: '__ContentStudentUpdated__',
|
|
388
|
+
mobile: '13812345678',
|
|
389
|
+
level: 1,
|
|
390
|
+
content: {
|
|
391
|
+
descriptionMarkdown: markdown,
|
|
392
|
+
descriptionHtml: '<p>Forged HTML</p>',
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
const studentContent = await studentContentModel.get({ studentId });
|
|
397
|
+
assert.equal(studentContent?.descriptionMarkdown, markdown);
|
|
398
|
+
assert.equal(studentContent?.descriptionHtml, app.bean.markdown.renderHtml(markdown));
|
|
399
|
+
assert.notEqual(studentContent?.descriptionHtml, '<p>Forged HTML</p>');
|
|
400
|
+
|
|
401
|
+
const whitespaceMarkdown = ' \n\t ';
|
|
402
|
+
await app.bean.executor.performAction('patch', '/training/student/:id', {
|
|
403
|
+
params: { id: studentId },
|
|
404
|
+
body: {
|
|
405
|
+
name: '__ContentStudentUpdated__',
|
|
406
|
+
mobile: '13812345678',
|
|
407
|
+
level: 1,
|
|
408
|
+
content: { descriptionMarkdown: whitespaceMarkdown },
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
const whitespaceContent = await studentContentModel.get({ studentId });
|
|
412
|
+
assert.equal(whitespaceContent?.descriptionMarkdown, whitespaceMarkdown);
|
|
413
|
+
assert.equal(
|
|
414
|
+
whitespaceContent?.descriptionHtml,
|
|
415
|
+
app.bean.markdown.renderHtml(whitespaceMarkdown),
|
|
416
|
+
);
|
|
417
|
+
} finally {
|
|
418
|
+
if (studentId) {
|
|
419
|
+
await app
|
|
420
|
+
.scope('training-student')
|
|
421
|
+
.model.studentContent.delete({ studentId }, { disableDeleted: true });
|
|
422
|
+
await app
|
|
423
|
+
.scope('training-student')
|
|
424
|
+
.model.student.deleteById(studentId, { disableDeleted: true });
|
|
425
|
+
}
|
|
426
|
+
await app.bean.passport.signout();
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
302
431
|
it('action:student:systemAdmin', async () => {
|
|
303
432
|
await app.bean.executor.mockCtx(async () => {
|
|
304
433
|
await app.bean.passport.signinMock();
|
|
@@ -327,7 +456,7 @@ describe('student.test.ts', () => {
|
|
|
327
456
|
await app.bean.executor.performAction('post', '/training/student', {
|
|
328
457
|
body: {
|
|
329
458
|
name: '__Tom__',
|
|
330
|
-
|
|
459
|
+
content: { descriptionMarkdown: 'This is a test' },
|
|
331
460
|
mobile: '13812345678',
|
|
332
461
|
level: 4,
|
|
333
462
|
},
|
|
@@ -344,7 +473,7 @@ describe('student.test.ts', () => {
|
|
|
344
473
|
await app.bean.executor.performAction('post', '/training/student', {
|
|
345
474
|
body: {
|
|
346
475
|
name: '__Tom__',
|
|
347
|
-
|
|
476
|
+
content: { descriptionMarkdown: 'This is a test' },
|
|
348
477
|
level: 1,
|
|
349
478
|
},
|
|
350
479
|
});
|
|
@@ -360,7 +489,7 @@ describe('student.test.ts', () => {
|
|
|
360
489
|
await app.bean.executor.performAction('post', '/training/student', {
|
|
361
490
|
body: {
|
|
362
491
|
name: '__Tom__',
|
|
363
|
-
|
|
492
|
+
content: { descriptionMarkdown: 'This is a test' },
|
|
364
493
|
mobile: '1381234567',
|
|
365
494
|
level: 1,
|
|
366
495
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.155",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "A vue3 framework with ioc",
|
|
6
6
|
"keywords": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"zova-core": "^5.1.88",
|
|
49
|
-
"zova-suite-a-zova": "^5.1.
|
|
49
|
+
"zova-suite-a-zova": "^5.1.154"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"clean-package": "^2.2.0",
|
package/zova/pnpm-lock.yaml
CHANGED
|
@@ -870,7 +870,7 @@ importers:
|
|
|
870
870
|
specifier: ^5.1.88
|
|
871
871
|
version: link:../zova-core
|
|
872
872
|
zova-suite-a-zova:
|
|
873
|
-
specifier: ^5.1.
|
|
873
|
+
specifier: ^5.1.153
|
|
874
874
|
version: link:../../src/suite-vendor/a-zova
|
|
875
875
|
devDependencies:
|
|
876
876
|
clean-package:
|
|
@@ -1076,7 +1076,7 @@ importers:
|
|
|
1076
1076
|
specifier: ^5.1.25
|
|
1077
1077
|
version: link:modules/a-fetch
|
|
1078
1078
|
zova-module-a-form:
|
|
1079
|
-
specifier: ^5.1.
|
|
1079
|
+
specifier: ^5.1.49
|
|
1080
1080
|
version: link:modules/a-form
|
|
1081
1081
|
zova-module-a-icon:
|
|
1082
1082
|
specifier: ^5.1.27
|
|
@@ -1804,6 +1804,10 @@ importers:
|
|
|
1804
1804
|
version: link:modules/training-student
|
|
1805
1805
|
|
|
1806
1806
|
src/suite/a-training/modules/training-student:
|
|
1807
|
+
dependencies:
|
|
1808
|
+
zova-module-basic-markdown:
|
|
1809
|
+
specifier: ^5.0.0
|
|
1810
|
+
version: link:../../../cabloy-basic/modules/basic-markdown
|
|
1807
1811
|
devDependencies:
|
|
1808
1812
|
clean-package:
|
|
1809
1813
|
specifier: ^2.2.0
|
|
@@ -11,6 +11,7 @@ export default function (): ZovaOpenapiConfigModule {
|
|
|
11
11
|
'CommerceTradeCart_clear',
|
|
12
12
|
'CommerceTradeCheckout_paymentMethods',
|
|
13
13
|
'CommerceTradeCheckout_create',
|
|
14
|
+
'CommerceTradeStockBalance_adjustStock',
|
|
14
15
|
'CommerceTradeOrder_select',
|
|
15
16
|
'CommerceTradeOrder_view',
|
|
16
17
|
'CommerceTradeOrder_ship',
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { TypeControllerInnerProps } from 'zova';
|
|
2
|
+
|
|
3
|
+
import { defineComponent } from 'vue';
|
|
4
|
+
import { prepareComponentOptions, useController } from 'zova';
|
|
5
|
+
|
|
6
|
+
import type { ControllerTableCellActionAdjustStockProps } from '../../component/tableCellActionAdjustStock/controller.jsx';
|
|
7
|
+
|
|
8
|
+
import { ControllerTableCellActionAdjustStock } from '../../component/tableCellActionAdjustStock/controller.jsx';
|
|
9
|
+
export type ZTableCellActionAdjustStockProps = {
|
|
10
|
+
controllerRef?: (ref: ControllerTableCellActionAdjustStock) => void;
|
|
11
|
+
} & ControllerTableCellActionAdjustStockProps;
|
|
12
|
+
|
|
13
|
+
type ControllerInnerProps = TypeControllerInnerProps<
|
|
14
|
+
ControllerTableCellActionAdjustStockProps,
|
|
15
|
+
keyof typeof ControllerTableCellActionAdjustStock.$propsDefault
|
|
16
|
+
>;
|
|
17
|
+
declare module 'zova-module-commerce-trade' {
|
|
18
|
+
export interface ControllerTableCellActionAdjustStock {
|
|
19
|
+
$props: ControllerInnerProps;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ZTableCellActionAdjustStock = defineComponent(
|
|
24
|
+
(_props: ZTableCellActionAdjustStockProps) => {
|
|
25
|
+
useController(ControllerTableCellActionAdjustStock, undefined, undefined);
|
|
26
|
+
return () => {};
|
|
27
|
+
},
|
|
28
|
+
prepareComponentOptions(),
|
|
29
|
+
);
|
|
30
|
+
declare module 'zova-module-a-bean' {
|
|
31
|
+
export interface IVonaComponentRecord {
|
|
32
|
+
'commerce-trade:tableCellActionAdjustStock': ControllerTableCellActionAdjustStockProps;
|
|
33
|
+
}
|
|
34
|
+
}
|