cabloy 5.1.149 → 5.1.151
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/commands/release.md +12 -1
- package/.claude/hooks/contract-loop-gate.ts +35 -17
- package/.claude/skills/cabloy-backend-scaffold/SKILL.md +4 -3
- package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +3 -2
- package/.claude/skills/cabloy-contract-loop/SKILL.md +4 -3
- package/.claude/skills/cabloy-domain-planning/SKILL.md +4 -3
- package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +5 -3
- package/.claude/skills/cabloy-master-detail/SKILL.md +4 -3
- package/.claude/skills/cabloy-module-removal/SKILL.md +5 -4
- package/.claude/skills/cabloy-resource-field-update/SKILL.md +8 -5
- package/.claude/skills/cabloy-spec-execution/SKILL.md +169 -0
- package/.claude/skills/cabloy-spec-execution/evals/evals.json +71 -0
- package/.claude/skills/cabloy-spec-execution/references/execution-protocol.md +137 -0
- package/.claude/skills/cabloy-spec-execution/references/status-and-evidence.md +117 -0
- package/.claude/skills/cabloy-spec-generation/SKILL.md +227 -0
- package/.claude/skills/cabloy-spec-generation/evals/evals.json +83 -0
- package/.claude/skills/cabloy-spec-generation/references/repo-aware-discovery.md +101 -0
- package/.claude/skills/cabloy-spec-generation/references/repo-specs-document-set.md +267 -0
- package/.claude/skills/cabloy-spec-generation/references/traceability-and-status-rules.md +135 -0
- package/.claude/skills/cabloy-spec-generation/scripts/generate-implementation-charts.mjs +350 -0
- package/.claude/skills/cabloy-spec-generation/scripts/generate-implementation-charts.test.mjs +202 -0
- package/.claude/skills/cabloy-workflow/SKILL.md +21 -10
- package/.claude/skills/cabloy-workflow/evals/evals.json +8 -2
- package/.claude/skills/cabloy-workflow/references/edition-detection.md +5 -3
- package/.claude/skills/cabloy-zova-source-reading/SKILL.md +4 -3
- package/CHANGELOG.md +41 -0
- package/CLAUDE.md +12 -11
- package/package.json +4 -2
- package/repo-docs/.vitepress/config.mjs +4 -0
- package/repo-docs/ai/docs-skills-rules-mapping.md +6 -6
- package/repo-docs/ai/future-skill-roadmap.md +2 -2
- package/repo-docs/ai/introduction.md +5 -5
- package/repo-docs/ai/playbook-contract-regeneration.md +3 -9
- package/repo-docs/ai/repo-guidance.md +5 -4
- package/repo-docs/ai/rules-and-config.md +2 -2
- package/repo-docs/ai/virtual-decorator-guidance.md +1 -3
- package/repo-docs/backend/cache-guide.md +36 -0
- package/repo-docs/backend/dto-guide.md +7 -6
- package/repo-docs/backend/dto-infer-generation.md +25 -5
- package/repo-docs/backend/introduction.md +1 -1
- package/repo-docs/backend/migration-and-changes.md +27 -2
- package/repo-docs/backend/model-guide.md +1 -1
- package/repo-docs/backend/resource-field-update.md +156 -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 +26 -9
- 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/router-tabs-introduction.md +2 -4
- 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/a-pay-payment-suite.md +1 -1
- package/repo-docs/fullstack/contract-loop-playbook.md +4 -0
- package/repo-docs/fullstack/one-to-one-companion-resource-guide.md +2 -2
- package/repo-docs/reference/package-map.md +1 -1
- package/repo-e2e/specs/a-commerce.spec.ts +40 -0
- package/scripts/release.ts +48 -1
- package/scripts/upgrade.ts +18 -7
- 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/src/.metadata/index.ts +2 -2
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +7 -6
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSummary.tsx +1 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +7 -6
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +10 -7
- package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +4 -4
- package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +39 -67
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +56 -37
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +5 -5
- 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/src/api/openapi/schemas.ts +8 -8
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +11 -11
- package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +2 -2
- 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 +58 -9
- 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
|
@@ -4,19 +4,47 @@ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
|
|
|
4
4
|
import { Api, v } from 'vona-module-a-openapiutils';
|
|
5
5
|
import { Dto } from 'vona-module-a-web';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
+
import { ZovaRender } from 'zova-rest-cabloy-basic-admin';
|
|
7
8
|
|
|
8
9
|
import { $locale } from '../.metadata/locales.ts';
|
|
9
10
|
|
|
10
11
|
export interface IDtoOptionsStockAdjust extends IDecoratorDtoOptions {}
|
|
11
12
|
|
|
12
|
-
@Dto<IDtoOptionsStockAdjust>({
|
|
13
|
+
@Dto<IDtoOptionsStockAdjust>({
|
|
14
|
+
openapi: { title: $locale('AdjustStock') },
|
|
15
|
+
blocks: [
|
|
16
|
+
ZovaRender.block('basic-pageentry:blockPageEntry', {
|
|
17
|
+
blocks: [
|
|
18
|
+
ZovaRender.block('basic-pageentry:blockForm', {
|
|
19
|
+
blocks: [
|
|
20
|
+
ZovaRender.block('basic-form:blockFormLayout', {
|
|
21
|
+
formLayout: {
|
|
22
|
+
children: [
|
|
23
|
+
{
|
|
24
|
+
type: 'section',
|
|
25
|
+
layout: 'flow',
|
|
26
|
+
children: [
|
|
27
|
+
{ type: 'field', name: 'delta' },
|
|
28
|
+
{ type: 'field', name: 'reason' },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
],
|
|
35
|
+
}),
|
|
36
|
+
],
|
|
37
|
+
}),
|
|
38
|
+
],
|
|
39
|
+
})
|
|
13
40
|
export class DtoStockAdjust {
|
|
14
|
-
@Api.field(v.title($locale('SkuId')), v.required(), v.tableIdentity())
|
|
41
|
+
@Api.field(v.title($locale('SkuId')), v.required(), v.tableIdentity(), ZovaRender.visible(false))
|
|
15
42
|
skuId: TableIdentity;
|
|
16
43
|
|
|
17
44
|
@Api.field(
|
|
18
45
|
v.title($locale('Delta')),
|
|
19
46
|
v.required(),
|
|
47
|
+
ZovaRender.field('basic-input:formFieldInput', { type: 'number' }),
|
|
20
48
|
z
|
|
21
49
|
.number()
|
|
22
50
|
.int()
|
|
@@ -27,6 +55,12 @@ export class DtoStockAdjust {
|
|
|
27
55
|
@Api.field(v.title($locale('Reason')), v.required(), v.min(1), v.max(255))
|
|
28
56
|
reason: string;
|
|
29
57
|
|
|
30
|
-
@Api.field(
|
|
58
|
+
@Api.field(
|
|
59
|
+
v.title($locale('CorrelationId')),
|
|
60
|
+
v.required(),
|
|
61
|
+
v.min(1),
|
|
62
|
+
v.max(100),
|
|
63
|
+
ZovaRender.visible(false),
|
|
64
|
+
)
|
|
31
65
|
correlationId: string;
|
|
32
66
|
}
|
|
@@ -116,8 +116,8 @@ declare module 'vona-module-training-student' {
|
|
|
116
116
|
export interface IModelOptionsStudent {
|
|
117
117
|
relations: {
|
|
118
118
|
trainingRecords: IModelRelationHasMany<'training-record:record', 'studentId', false, 'id'|'name'|'subjectCount'|'totalScore'|'averageScore'|'trainingTime'|'sceneImageIds'|'dossierFileIds'|'description', undefined, undefined, undefined>;
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
content: IModelRelationHasOne<'training-student:studentContent', 'studentId', false, 'id'|'descriptionMarkdown'>;
|
|
120
|
+
contentHtml: IModelRelationHasOne<'training-student:studentContent', 'studentId', false, 'id'|'descriptionHtml'>;
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
123
|
export interface IModelOptionsStudentContent {
|
|
@@ -10,12 +10,12 @@ import { ModelStudent } from '../model/student.ts';
|
|
|
10
10
|
import { DtoDetailRecordMutate } from './detailRecordMutate.tsx';
|
|
11
11
|
import { DtoDetailRecordResItem } from './detailRecordResItem.tsx';
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
ZovaRender.fieldSource('
|
|
13
|
+
const studentContentField = $makeMetadata(
|
|
14
|
+
ZovaRender.fieldSource('content.descriptionMarkdown'),
|
|
15
15
|
ZovaRender.field('basic-markdown:formFieldMarkdown'),
|
|
16
16
|
);
|
|
17
17
|
|
|
18
|
-
export interface IDtoOptionsStudentCreate extends IDecoratorDtoOptions<'
|
|
18
|
+
export interface IDtoOptionsStudentCreate extends IDecoratorDtoOptions<'content'> {}
|
|
19
19
|
|
|
20
20
|
@Dto<IDtoOptionsStudentCreate>({
|
|
21
21
|
blocks: [
|
|
@@ -54,7 +54,7 @@ export interface IDtoOptionsStudentCreate extends IDecoratorDtoOptions<'studentC
|
|
|
54
54
|
children: [
|
|
55
55
|
{
|
|
56
56
|
type: 'section',
|
|
57
|
-
children: [{ type: 'field', name: '
|
|
57
|
+
children: [{ type: 'field', name: 'content' }],
|
|
58
58
|
},
|
|
59
59
|
],
|
|
60
60
|
},
|
|
@@ -93,7 +93,7 @@ export interface IDtoOptionsStudentCreate extends IDecoratorDtoOptions<'studentC
|
|
|
93
93
|
}),
|
|
94
94
|
],
|
|
95
95
|
fields: {
|
|
96
|
-
|
|
96
|
+
content: studentContentField,
|
|
97
97
|
trainingRecords: $makeMetadata(
|
|
98
98
|
v.title($locale('TrainingRecords')),
|
|
99
99
|
ZovaRender.order(6),
|
|
@@ -103,8 +103,9 @@ export interface IDtoOptionsStudentCreate extends IDecoratorDtoOptions<'studentC
|
|
|
103
103
|
},
|
|
104
104
|
})
|
|
105
105
|
export class DtoStudentCreate extends $Dto.create(() => ModelStudent, {
|
|
106
|
+
columns: ['name', 'mobile', 'imageId', 'level'],
|
|
106
107
|
include: {
|
|
107
|
-
|
|
108
|
+
content: { columns: ['descriptionMarkdown'] },
|
|
108
109
|
trainingRecords: { dtoClass: DtoDetailRecordMutate },
|
|
109
110
|
},
|
|
110
111
|
}) {
|
|
@@ -14,7 +14,7 @@ export class DtoStudentSummary extends $Dto.get(() => ModelStudent, {
|
|
|
14
14
|
columns: ['id', 'name', 'mobile', 'level'],
|
|
15
15
|
}) {
|
|
16
16
|
@Api.field(v.title($locale('Description')), v.optional())
|
|
17
|
-
|
|
17
|
+
descriptionMarkdown?: string;
|
|
18
18
|
|
|
19
19
|
@Api.field(v.title($locale('DescriptionHtml')), v.optional())
|
|
20
20
|
descriptionHtml?: string;
|
|
@@ -10,12 +10,12 @@ import { ModelStudent } from '../model/student.ts';
|
|
|
10
10
|
import { DtoDetailRecordMutate } from './detailRecordMutate.tsx';
|
|
11
11
|
import { DtoDetailRecordResItem } from './detailRecordResItem.tsx';
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
ZovaRender.fieldSource('
|
|
13
|
+
const studentContentField = $makeMetadata(
|
|
14
|
+
ZovaRender.fieldSource('content.descriptionMarkdown'),
|
|
15
15
|
ZovaRender.field('basic-markdown:formFieldMarkdown'),
|
|
16
16
|
);
|
|
17
17
|
|
|
18
|
-
export interface IDtoOptionsStudentUpdate extends IDecoratorDtoOptions<'
|
|
18
|
+
export interface IDtoOptionsStudentUpdate extends IDecoratorDtoOptions<'content'> {}
|
|
19
19
|
|
|
20
20
|
@Dto<IDtoOptionsStudentUpdate>({
|
|
21
21
|
blocks: [
|
|
@@ -54,7 +54,7 @@ export interface IDtoOptionsStudentUpdate extends IDecoratorDtoOptions<'studentC
|
|
|
54
54
|
children: [
|
|
55
55
|
{
|
|
56
56
|
type: 'section',
|
|
57
|
-
children: [{ type: 'field', name: '
|
|
57
|
+
children: [{ type: 'field', name: 'content' }],
|
|
58
58
|
},
|
|
59
59
|
],
|
|
60
60
|
},
|
|
@@ -90,7 +90,7 @@ export interface IDtoOptionsStudentUpdate extends IDecoratorDtoOptions<'studentC
|
|
|
90
90
|
}),
|
|
91
91
|
],
|
|
92
92
|
fields: {
|
|
93
|
-
|
|
93
|
+
content: studentContentField,
|
|
94
94
|
trainingRecords: $makeMetadata(
|
|
95
95
|
v.title($locale('TrainingRecords')),
|
|
96
96
|
ZovaRender.order(6),
|
|
@@ -99,8 +99,9 @@ export interface IDtoOptionsStudentUpdate extends IDecoratorDtoOptions<'studentC
|
|
|
99
99
|
},
|
|
100
100
|
})
|
|
101
101
|
export class DtoStudentUpdate extends $Dto.update(() => ModelStudent, {
|
|
102
|
+
columns: ['name', 'mobile', 'imageId', 'level'],
|
|
102
103
|
include: {
|
|
103
|
-
|
|
104
|
+
content: { columns: ['descriptionMarkdown'] },
|
|
104
105
|
trainingRecords: { dtoClass: DtoDetailRecordMutate },
|
|
105
106
|
},
|
|
106
107
|
}) {
|
|
@@ -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,12 +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
|
-
const
|
|
15
|
-
ZovaRender.fieldSource('
|
|
15
|
+
const studentContentField = $makeSchema(
|
|
16
|
+
ZovaRender.fieldSource('content.descriptionMarkdown'),
|
|
16
17
|
ZovaRender.field('basic-markdown:formFieldMarkdown'),
|
|
18
|
+
v.optional(),
|
|
19
|
+
z.string(),
|
|
17
20
|
);
|
|
18
21
|
|
|
19
|
-
export interface IDtoOptionsStudentView extends IDecoratorDtoOptions<'
|
|
22
|
+
export interface IDtoOptionsStudentView extends IDecoratorDtoOptions<'_descriptionMarkdown'> {}
|
|
20
23
|
|
|
21
24
|
@Dto<IDtoOptionsStudentView>({
|
|
22
25
|
blocks: [
|
|
@@ -55,7 +58,7 @@ export interface IDtoOptionsStudentView extends IDecoratorDtoOptions<'studentCon
|
|
|
55
58
|
children: [
|
|
56
59
|
{
|
|
57
60
|
type: 'section',
|
|
58
|
-
children: [{ type: 'field', name: '
|
|
61
|
+
children: [{ type: 'field', name: '_descriptionMarkdown' }],
|
|
59
62
|
},
|
|
60
63
|
],
|
|
61
64
|
},
|
|
@@ -88,7 +91,7 @@ export interface IDtoOptionsStudentView extends IDecoratorDtoOptions<'studentCon
|
|
|
88
91
|
}),
|
|
89
92
|
],
|
|
90
93
|
fields: {
|
|
91
|
-
|
|
94
|
+
_descriptionMarkdown: studentContentField,
|
|
92
95
|
trainingRecords: $makeMetadata(
|
|
93
96
|
v.title($locale('TrainingRecords')),
|
|
94
97
|
ZovaRender.order(6),
|
|
@@ -98,7 +101,7 @@ export interface IDtoOptionsStudentView extends IDecoratorDtoOptions<'studentCon
|
|
|
98
101
|
})
|
|
99
102
|
export class DtoStudentView extends $Dto.get(() => ModelStudent, {
|
|
100
103
|
include: {
|
|
101
|
-
|
|
104
|
+
content: { columns: ['descriptionMarkdown'] },
|
|
102
105
|
trainingRecords: { dtoClass: DtoDetailRecordView },
|
|
103
106
|
},
|
|
104
107
|
}) {
|
|
@@ -22,11 +22,11 @@ export interface IModelOptionsStudent extends IDecoratorModelOptions<EntityStude
|
|
|
22
22
|
'description',
|
|
23
23
|
],
|
|
24
24
|
}),
|
|
25
|
-
|
|
26
|
-
columns: ['id', '
|
|
25
|
+
content: $relation.hasOne('training-student:studentContent', 'studentId', {
|
|
26
|
+
columns: ['id', 'descriptionMarkdown'],
|
|
27
27
|
}),
|
|
28
|
-
|
|
29
|
-
columns: ['id', '
|
|
28
|
+
contentHtml: $relation.hasOne('training-student:studentContent', 'studentId', {
|
|
29
|
+
columns: ['id', 'descriptionHtml'],
|
|
30
30
|
}),
|
|
31
31
|
},
|
|
32
32
|
})
|
|
@@ -12,33 +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 {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return { studentContentForm: true };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function getStudentContentInclude() {
|
|
26
|
-
return { studentContent: true };
|
|
23
|
+
return {
|
|
24
|
+
content: true,
|
|
25
|
+
trainingRecords: { include: { trainingRecordSubjects: true } },
|
|
26
|
+
};
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
@Service()
|
|
30
30
|
export class ServiceStudent extends BeanBase {
|
|
31
31
|
async create(student: DtoStudentCreate): Promise<EntityStudent> {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
await this._updateStudentContent(created.id, studentContentForm.descriptionMarkdown);
|
|
40
|
-
}
|
|
41
|
-
return created;
|
|
32
|
+
return await this.scope.model.student.insert(
|
|
33
|
+
{
|
|
34
|
+
...student,
|
|
35
|
+
content: this._prepareContent(student.content),
|
|
36
|
+
},
|
|
37
|
+
{ include: getStudentRelationsInclude() },
|
|
38
|
+
);
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
async select(params?: IQueryParams<ModelStudent>): Promise<DtoStudentSelectRes> {
|
|
@@ -47,34 +44,28 @@ export class ServiceStudent extends BeanBase {
|
|
|
47
44
|
|
|
48
45
|
async view(id: TableIdentity): Promise<DtoStudentView | undefined> {
|
|
49
46
|
return await this.scope.model.student.getById(id, {
|
|
50
|
-
include:
|
|
51
|
-
...getStudentRecordSubjectsInclude(),
|
|
52
|
-
...getStudentContentFormInclude(),
|
|
53
|
-
},
|
|
47
|
+
include: getStudentRelationsInclude(),
|
|
54
48
|
});
|
|
55
49
|
}
|
|
56
50
|
|
|
57
51
|
async update(id: TableIdentity, student: DtoStudentUpdate) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
return updateResult;
|
|
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
|
+
);
|
|
68
60
|
}
|
|
69
61
|
|
|
70
62
|
async summary(id: TableIdentity): Promise<DtoStudentSummary | undefined> {
|
|
71
63
|
const student = await this.scope.model.student.getById(id, {
|
|
72
|
-
include:
|
|
64
|
+
include: { content: true, contentHtml: true },
|
|
73
65
|
});
|
|
74
66
|
if (!student) return undefined;
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const descriptionLength = description?.length ?? 0;
|
|
67
|
+
const descriptionMarkdown = student.content?.descriptionMarkdown;
|
|
68
|
+
const descriptionLength = descriptionMarkdown?.length ?? 0;
|
|
78
69
|
const levelTitle = String(student.level);
|
|
79
70
|
return {
|
|
80
71
|
id: student.id,
|
|
@@ -82,50 +73,31 @@ export class ServiceStudent extends BeanBase {
|
|
|
82
73
|
mobile: student.mobile,
|
|
83
74
|
level: student.level,
|
|
84
75
|
levelTitle,
|
|
85
|
-
|
|
86
|
-
descriptionHtml,
|
|
76
|
+
descriptionMarkdown,
|
|
77
|
+
descriptionHtml: student.contentHtml?.descriptionHtml,
|
|
87
78
|
descriptionLength,
|
|
88
79
|
summaryText: `${student.name} is in level ${student.level}. Description length: ${descriptionLength}.`,
|
|
89
80
|
};
|
|
90
81
|
}
|
|
91
82
|
|
|
92
83
|
async delete(id: TableIdentity) {
|
|
93
|
-
await this.scope.model.studentContent.delete({ studentId: id });
|
|
94
84
|
return await this.scope.model.student.deleteById(id, {
|
|
95
|
-
include:
|
|
85
|
+
include: getStudentRelationsInclude(),
|
|
96
86
|
});
|
|
97
87
|
}
|
|
98
88
|
|
|
99
89
|
async deleteForce(id: TableIdentity) {
|
|
100
|
-
await this.scope.model.studentContent.delete({ studentId: id }, { disableDeleted: true });
|
|
101
90
|
return await this.scope.model.student.deleteById(id, {
|
|
102
91
|
disableDeleted: true,
|
|
103
|
-
include:
|
|
92
|
+
include: getStudentRelationsInclude(),
|
|
104
93
|
});
|
|
105
94
|
}
|
|
106
95
|
|
|
107
|
-
private
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (!markdown) {
|
|
114
|
-
if (studentContent) await this.scope.model.studentContent.deleteById(studentContent.id);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
const descriptionHtml = this.bean.markdown.renderHtml(markdown);
|
|
118
|
-
if (studentContent) {
|
|
119
|
-
await this.scope.model.studentContent.updateById(studentContent.id, {
|
|
120
|
-
descriptionMarkdown: markdown,
|
|
121
|
-
descriptionHtml,
|
|
122
|
-
});
|
|
123
|
-
} else {
|
|
124
|
-
await this.scope.model.studentContent.insert({
|
|
125
|
-
studentId,
|
|
126
|
-
descriptionMarkdown: markdown,
|
|
127
|
-
descriptionHtml,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
96
|
+
private _prepareContent(content: StudentContentInput | undefined) {
|
|
97
|
+
const descriptionMarkdown = content?.descriptionMarkdown ?? '';
|
|
98
|
+
return {
|
|
99
|
+
descriptionMarkdown,
|
|
100
|
+
descriptionHtml: this.bean.markdown.renderHtml(descriptionMarkdown),
|
|
101
|
+
};
|
|
130
102
|
}
|
|
131
103
|
}
|
|
@@ -43,18 +43,18 @@ describe('student.test.ts', () => {
|
|
|
43
43
|
profileSection?.children.map(item => item.name),
|
|
44
44
|
['name', 'mobile', 'imageId'],
|
|
45
45
|
);
|
|
46
|
+
const descriptionFieldName =
|
|
47
|
+
DtoClass === DtoStudentView ? '_descriptionMarkdown' : 'content';
|
|
46
48
|
assert.deepEqual(
|
|
47
49
|
studentContentSection?.children.map(item => item.name),
|
|
48
|
-
[
|
|
49
|
-
);
|
|
50
|
-
assert.equal(
|
|
51
|
-
component.properties.studentContentForm.rest.fieldSource,
|
|
52
|
-
'studentContentForm.descriptionMarkdown',
|
|
53
|
-
);
|
|
54
|
-
assert.equal(
|
|
55
|
-
component.properties.studentContentForm.rest.form.render,
|
|
56
|
-
'basic-markdown:formFieldMarkdown',
|
|
50
|
+
[descriptionFieldName],
|
|
57
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
|
+
}
|
|
58
58
|
assert.equal(tabs?.children[1]?.children[0]?.name, 'level');
|
|
59
59
|
assert.deepEqual(
|
|
60
60
|
trainingRecordsSection?.children.map(item => item.name),
|
|
@@ -111,7 +111,7 @@ describe('student.test.ts', () => {
|
|
|
111
111
|
'name',
|
|
112
112
|
'mobile',
|
|
113
113
|
'level',
|
|
114
|
-
'
|
|
114
|
+
'descriptionMarkdown',
|
|
115
115
|
'descriptionHtml',
|
|
116
116
|
'levelTitle',
|
|
117
117
|
'descriptionLength',
|
|
@@ -144,7 +144,7 @@ describe('student.test.ts', () => {
|
|
|
144
144
|
`## Updated profile\n\n${'This is updated Markdown content. '.repeat(10)}`.trim();
|
|
145
145
|
const data = {
|
|
146
146
|
name: '__Tom__',
|
|
147
|
-
|
|
147
|
+
content: { descriptionMarkdown: description },
|
|
148
148
|
mobile,
|
|
149
149
|
level: 1,
|
|
150
150
|
trainingRecords: [
|
|
@@ -207,7 +207,7 @@ describe('student.test.ts', () => {
|
|
|
207
207
|
const record = student.trainingRecords?.[0];
|
|
208
208
|
const recordSubject = record?.trainingRecordSubjects?.[0];
|
|
209
209
|
assert.equal(student.trainingRecords?.length, 1);
|
|
210
|
-
assert.equal(student.
|
|
210
|
+
assert.equal(student.content?.descriptionMarkdown, description);
|
|
211
211
|
assert.equal(record?.name, '__Record__');
|
|
212
212
|
assert.equal(record?.subjectCount, 1);
|
|
213
213
|
assert.equal(record?.totalScore, 88);
|
|
@@ -219,7 +219,7 @@ describe('student.test.ts', () => {
|
|
|
219
219
|
// update
|
|
220
220
|
const dataUpdate = {
|
|
221
221
|
name: '__TomNew__',
|
|
222
|
-
|
|
222
|
+
content: { descriptionMarkdown: descriptionUpdate },
|
|
223
223
|
mobile: mobileUpdate,
|
|
224
224
|
level: 2,
|
|
225
225
|
trainingRecords: [
|
|
@@ -262,7 +262,7 @@ describe('student.test.ts', () => {
|
|
|
262
262
|
assert.equal(student.name, dataUpdate.name);
|
|
263
263
|
assert.equal(student.level, dataUpdate.level);
|
|
264
264
|
assert.equal(student.mobile, maskedMobileUpdate);
|
|
265
|
-
assert.equal(student.
|
|
265
|
+
assert.equal(student.content?.descriptionMarkdown, descriptionUpdate);
|
|
266
266
|
assert.equal(student.trainingRecords?.length, 1);
|
|
267
267
|
assert.equal(updatedRecord?.name, '__RecordNew__');
|
|
268
268
|
assert.equal(updatedRecord?.subjectCount, 2);
|
|
@@ -299,7 +299,7 @@ describe('student.test.ts', () => {
|
|
|
299
299
|
assert.equal(summary.name, dataUpdate.name);
|
|
300
300
|
assert.equal(summary.mobile, maskedMobileUpdate);
|
|
301
301
|
assert.equal(summary.level, dataUpdate.level);
|
|
302
|
-
assert.equal(summary.
|
|
302
|
+
assert.equal(summary.descriptionMarkdown, descriptionUpdate);
|
|
303
303
|
assert.equal(summary.descriptionHtml, app.bean.markdown.renderHtml(descriptionUpdate));
|
|
304
304
|
assert.equal(summary.descriptionLength, descriptionUpdate.length);
|
|
305
305
|
assert.equal(typeof summary.levelTitle, 'string');
|
|
@@ -309,6 +309,14 @@ describe('student.test.ts', () => {
|
|
|
309
309
|
params: { id: student.id },
|
|
310
310
|
});
|
|
311
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
|
+
);
|
|
312
320
|
// findOne
|
|
313
321
|
student = await app.bean.executor.performAction('get', '/training/student/:id', {
|
|
314
322
|
params: { id: student.id },
|
|
@@ -355,45 +363,56 @@ describe('student.test.ts', () => {
|
|
|
355
363
|
level: 1,
|
|
356
364
|
},
|
|
357
365
|
});
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
);
|
|
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, '');
|
|
362
370
|
|
|
363
371
|
await app.bean.executor.performAction('patch', '/training/student/:id', {
|
|
364
372
|
params: { id: studentId },
|
|
365
373
|
body: {
|
|
366
|
-
name: '
|
|
374
|
+
name: '__ContentStudentUpdated__',
|
|
367
375
|
mobile: '13812345678',
|
|
368
376
|
level: 1,
|
|
369
|
-
|
|
370
|
-
|
|
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,
|
|
371
392
|
descriptionHtml: '<p>Forged HTML</p>',
|
|
372
393
|
},
|
|
373
394
|
},
|
|
374
395
|
});
|
|
375
|
-
const studentContent = await
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
assert.equal(studentContent?.descriptionMarkdown, '## Created later');
|
|
379
|
-
assert.equal(
|
|
380
|
-
studentContent?.descriptionHtml,
|
|
381
|
-
app.bean.markdown.renderHtml('## Created later'),
|
|
382
|
-
);
|
|
396
|
+
const studentContent = await studentContentModel.get({ studentId });
|
|
397
|
+
assert.equal(studentContent?.descriptionMarkdown, markdown);
|
|
398
|
+
assert.equal(studentContent?.descriptionHtml, app.bean.markdown.renderHtml(markdown));
|
|
383
399
|
assert.notEqual(studentContent?.descriptionHtml, '<p>Forged HTML</p>');
|
|
384
400
|
|
|
401
|
+
const whitespaceMarkdown = ' \n\t ';
|
|
385
402
|
await app.bean.executor.performAction('patch', '/training/student/:id', {
|
|
386
403
|
params: { id: studentId },
|
|
387
404
|
body: {
|
|
388
|
-
name: '
|
|
405
|
+
name: '__ContentStudentUpdated__',
|
|
389
406
|
mobile: '13812345678',
|
|
390
407
|
level: 1,
|
|
391
|
-
|
|
408
|
+
content: { descriptionMarkdown: whitespaceMarkdown },
|
|
392
409
|
},
|
|
393
410
|
});
|
|
411
|
+
const whitespaceContent = await studentContentModel.get({ studentId });
|
|
412
|
+
assert.equal(whitespaceContent?.descriptionMarkdown, whitespaceMarkdown);
|
|
394
413
|
assert.equal(
|
|
395
|
-
|
|
396
|
-
|
|
414
|
+
whitespaceContent?.descriptionHtml,
|
|
415
|
+
app.bean.markdown.renderHtml(whitespaceMarkdown),
|
|
397
416
|
);
|
|
398
417
|
} finally {
|
|
399
418
|
if (studentId) {
|
|
@@ -437,7 +456,7 @@ describe('student.test.ts', () => {
|
|
|
437
456
|
await app.bean.executor.performAction('post', '/training/student', {
|
|
438
457
|
body: {
|
|
439
458
|
name: '__Tom__',
|
|
440
|
-
|
|
459
|
+
content: { descriptionMarkdown: 'This is a test' },
|
|
441
460
|
mobile: '13812345678',
|
|
442
461
|
level: 4,
|
|
443
462
|
},
|
|
@@ -454,7 +473,7 @@ describe('student.test.ts', () => {
|
|
|
454
473
|
await app.bean.executor.performAction('post', '/training/student', {
|
|
455
474
|
body: {
|
|
456
475
|
name: '__Tom__',
|
|
457
|
-
|
|
476
|
+
content: { descriptionMarkdown: 'This is a test' },
|
|
458
477
|
level: 1,
|
|
459
478
|
},
|
|
460
479
|
});
|
|
@@ -470,7 +489,7 @@ describe('student.test.ts', () => {
|
|
|
470
489
|
await app.bean.executor.performAction('post', '/training/student', {
|
|
471
490
|
body: {
|
|
472
491
|
name: '__Tom__',
|
|
473
|
-
|
|
492
|
+
content: { descriptionMarkdown: 'This is a test' },
|
|
474
493
|
mobile: '1381234567',
|
|
475
494
|
level: 1,
|
|
476
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.154
|
|
874
874
|
version: link:../../src/suite-vendor/a-zova
|
|
875
875
|
devDependencies:
|
|
876
876
|
clean-package:
|
|
@@ -977,7 +977,7 @@ importers:
|
|
|
977
977
|
specifier: ^5.0.1
|
|
978
978
|
version: link:modules/a-rbac
|
|
979
979
|
zova-module-rest-resource:
|
|
980
|
-
specifier: ^5.1.
|
|
980
|
+
specifier: ^5.1.47
|
|
981
981
|
version: link:modules/rest-resource
|
|
982
982
|
|
|
983
983
|
src/suite-vendor/a-cabloy/modules/a-rbac:
|
|
@@ -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.50
|
|
1080
1080
|
version: link:modules/a-form
|
|
1081
1081
|
zova-module-a-icon:
|
|
1082
1082
|
specifier: ^5.1.27
|
|
@@ -1091,10 +1091,10 @@ importers:
|
|
|
1091
1091
|
specifier: ^5.1.22
|
|
1092
1092
|
version: link:modules/a-meta
|
|
1093
1093
|
zova-module-a-model:
|
|
1094
|
-
specifier: ^5.1.
|
|
1094
|
+
specifier: ^5.1.36
|
|
1095
1095
|
version: link:modules/a-model
|
|
1096
1096
|
zova-module-a-openapi:
|
|
1097
|
-
specifier: ^5.1.
|
|
1097
|
+
specifier: ^5.1.51
|
|
1098
1098
|
version: link:modules/a-openapi
|
|
1099
1099
|
zova-module-a-router:
|
|
1100
1100
|
specifier: ^5.1.34
|