cabloy 5.1.126 → 5.1.127
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/CHANGELOG.md +21 -0
- package/cabloy-docs/backend/error-guide.md +17 -0
- package/e2e/specs/a-commerce/commerce.spec.ts +135 -4
- package/package.json +1 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/bean/resource/error/errorClass.ts +21 -4
- package/vona/packages-vona/vona-core/src/lib/bean/resource/error/errorObject.ts +1 -0
- package/vona/packages-vona/vona-core/src/lib/bean/type.ts +3 -1
- package/vona/packages-vona/vona-core/src/types/interface/module.ts +6 -1
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +5 -5
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +36 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categorySelectResItem.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categoryView.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/category.tsx +21 -2
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/category.ts +11 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/category.ts +7 -2
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/categoryPresentation.test.ts +218 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundResult.tsx +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +2 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +111 -3
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/schemas.ts +8 -4
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/types.ts +65 -19
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +7 -6
- package/zova/src/suite/cabloy-basic/modules/basic-select/src/bean/tableCell.select.tsx +12 -6
- package/zova/src/suite/cabloy-basic/modules/basic-select/src/component/formFieldSelect/controller.tsx +6 -3
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
1
|
+
5.1.127
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.1.127
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Separate module error codes from HTTP status codes.
|
|
8
|
+
- Complete the commerce payment refund flow.
|
|
9
|
+
- Update feature functionality.
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
- Harden the commerce order detail locator.
|
|
14
|
+
|
|
15
|
+
### Improvements
|
|
16
|
+
|
|
17
|
+
- Refactor error handling.
|
|
18
|
+
- Record the first phase of error status evolution.
|
|
19
|
+
- Define product presentation authority.
|
|
20
|
+
- Retain category semantic CI evidence.
|
|
21
|
+
- Retain semantic presentation CI evidence.
|
|
22
|
+
- Prepare the category presentation rollout.
|
|
23
|
+
|
|
3
24
|
## 5.1.126
|
|
4
25
|
|
|
5
26
|
### Features
|
|
@@ -37,6 +37,23 @@ export const errors = {
|
|
|
37
37
|
|
|
38
38
|
A useful convention is that business error codes should stay above `1000`.
|
|
39
39
|
|
|
40
|
+
### Structured declarations and HTTP status
|
|
41
|
+
|
|
42
|
+
Legacy numeric declarations remain supported. When a business error needs a stable namespaced application code and an intentional HTTP status, use a descriptor:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
export const errors = {
|
|
46
|
+
ErrorTest: {
|
|
47
|
+
code: 1001,
|
|
48
|
+
status: 409,
|
|
49
|
+
},
|
|
50
|
+
} as const;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The descriptor produces an application error code such as `training-student:1001` and an HTTP response status of `409`. The `code` is the stable business identity; `status` is the initial transport decision. The standard JSON response remains `{ code, message }`, with the HTTP status carried by the response envelope.
|
|
54
|
+
|
|
55
|
+
Use statuses that describe the API meaning: `404` for an absent resource, `409` for a resource-state or uniqueness conflict, and `422` for unacceptable business content. Existing final error filters may still revise the initial status for request-context policy, such as converting unauthenticated authorization failures from `403` to `401`.
|
|
56
|
+
|
|
40
57
|
### 2. Define the localized error messages
|
|
41
58
|
|
|
42
59
|
Representative locale pattern:
|
|
@@ -535,7 +535,9 @@ test(
|
|
|
535
535
|
await orderCard.getByRole('button', { name: 'View', exact: true }).click();
|
|
536
536
|
await expect(page).toHaveURL(new RegExp(`/commerce/order/${checkout.orderId}(?:/|$)`));
|
|
537
537
|
await historyDetailResponse;
|
|
538
|
-
await expect(
|
|
538
|
+
await expect(
|
|
539
|
+
page.getByRole('heading', { name: `Order #${checkout.orderId}`, level: 1 }),
|
|
540
|
+
).toBeVisible();
|
|
539
541
|
await expect(page.getByText('1 × $45.99 = $45.99')).toBeVisible();
|
|
540
542
|
await expect(page.getByRole('alert')).toHaveCount(0);
|
|
541
543
|
expect(pageErrors).toEqual([]);
|
|
@@ -705,11 +707,39 @@ test(
|
|
|
705
707
|
const executeResponse = waitForApiResponse(
|
|
706
708
|
adminPage,
|
|
707
709
|
'POST',
|
|
708
|
-
`/api/commerce/trade/order/${checkout.orderId}/
|
|
710
|
+
`/api/commerce/trade/order/${checkout.orderId}/executeRefund`,
|
|
709
711
|
);
|
|
710
712
|
await orderRow.getByRole('button', { name: 'Execute refund', exact: true }).click();
|
|
711
|
-
|
|
712
|
-
|
|
713
|
+
const executeResponseValue = await executeResponse;
|
|
714
|
+
expect(executeResponseValue.ok()).toBeTruthy();
|
|
715
|
+
const executeResult = (await executeResponseValue.json()).data;
|
|
716
|
+
const authorization = executeResponseValue.request().headers()['authorization'];
|
|
717
|
+
expect(authorization).toMatch(/^Bearer /);
|
|
718
|
+
const authHeaders = { Authorization: authorization };
|
|
719
|
+
expectTableIdentity(executeResult.refundAttemptId);
|
|
720
|
+
expectTableIdentity(executeResult.refundOperationId);
|
|
721
|
+
expect([
|
|
722
|
+
executeResult.orderState,
|
|
723
|
+
executeResult.refundState,
|
|
724
|
+
executeResult.refundAttemptState,
|
|
725
|
+
]).toEqual(['refund_approved', 'approved', 'created']);
|
|
726
|
+
const completeRefundResponse = await adminPage.request.post(
|
|
727
|
+
`/api/pay/mock/payment-session/refund-operation/${executeResult.refundOperationId}/complete`,
|
|
728
|
+
{ headers: authHeaders, data: { outcome: 'succeeded' } },
|
|
729
|
+
);
|
|
730
|
+
expect(completeRefundResponse.ok()).toBeTruthy();
|
|
731
|
+
await expect
|
|
732
|
+
.poll(
|
|
733
|
+
async () => {
|
|
734
|
+
await adminPage.reload({ waitUntil: 'load' });
|
|
735
|
+
const currentOrderRow = await getAdminOrderRow(adminPage, checkout.orderId);
|
|
736
|
+
return (await currentOrderRow.textContent()) ?? '';
|
|
737
|
+
},
|
|
738
|
+
{ timeout: 40_000 },
|
|
739
|
+
)
|
|
740
|
+
.toContain('refunded');
|
|
741
|
+
const refundedOrderRow = await getAdminOrderRow(adminPage, checkout.orderId);
|
|
742
|
+
await expect(refundedOrderRow).toContainText('refunded');
|
|
713
743
|
expect(adminPageErrors).toEqual([]);
|
|
714
744
|
} finally {
|
|
715
745
|
await adminContext.close().catch(() => {});
|
|
@@ -885,6 +915,107 @@ test(
|
|
|
885
915
|
},
|
|
886
916
|
);
|
|
887
917
|
|
|
918
|
+
test(
|
|
919
|
+
'ATP-SPC-02: Category renders semantic Admin relation and publication controls',
|
|
920
|
+
{ tag: ['@admin', '@flow', '@category'] },
|
|
921
|
+
async ({ browser }, testInfo) => {
|
|
922
|
+
test.setTimeout(60_000);
|
|
923
|
+
const suffix = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
924
|
+
const parentName = `E2E Category Parent ${suffix}`;
|
|
925
|
+
const childName = `E2E Category Child ${suffix}`;
|
|
926
|
+
const adminContext = await browser.newContext();
|
|
927
|
+
const adminPage = await adminContext.newPage();
|
|
928
|
+
const adminPageErrors = collectPageErrors(adminPage);
|
|
929
|
+
const categoryCreatePath = '/commerce-admin/rest/resource/commerce-catalog%3Acategory/create';
|
|
930
|
+
const categoryListPath = '/commerce-admin/rest/resource/commerce-catalog%3Acategory';
|
|
931
|
+
const categoryActionPath = '/api/commerce/catalog/category';
|
|
932
|
+
let parentId: number | string | undefined;
|
|
933
|
+
let childId: number | string | undefined;
|
|
934
|
+
let headers: { Authorization: string } | undefined;
|
|
935
|
+
try {
|
|
936
|
+
await adminPage.setViewportSize({ width: 1440, height: 900 });
|
|
937
|
+
await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
|
|
938
|
+
const accessToken = (await adminContext.cookies()).find(
|
|
939
|
+
cookie => cookie.name === 'token',
|
|
940
|
+
)?.value;
|
|
941
|
+
expect(accessToken).toBeTruthy();
|
|
942
|
+
headers = { Authorization: `Bearer ${accessToken}` };
|
|
943
|
+
|
|
944
|
+
const parentCreateResponse = await adminPage.request.post(categoryActionPath, {
|
|
945
|
+
data: { name: parentName, published: false },
|
|
946
|
+
headers,
|
|
947
|
+
});
|
|
948
|
+
expect(parentCreateResponse.ok()).toBeTruthy();
|
|
949
|
+
parentId = (await parentCreateResponse.json()).data;
|
|
950
|
+
expectTableIdentity(parentId);
|
|
951
|
+
|
|
952
|
+
await adminPage.goto(categoryCreatePath, { waitUntil: 'load' });
|
|
953
|
+
const parentPublication = adminPage
|
|
954
|
+
.getByRole('group', { name: 'Published' })
|
|
955
|
+
.getByRole('combobox');
|
|
956
|
+
await expect(parentPublication).toBeVisible();
|
|
957
|
+
await expect(parentPublication.locator('option')).toHaveText([
|
|
958
|
+
'',
|
|
959
|
+
'Unpublished',
|
|
960
|
+
'Published',
|
|
961
|
+
]);
|
|
962
|
+
await parentPublication.selectOption({ label: 'Unpublished' });
|
|
963
|
+
await expect(parentPublication).toHaveValue('false');
|
|
964
|
+
|
|
965
|
+
const parentPicker = adminPage
|
|
966
|
+
.getByRole('group', { name: 'Parent category' })
|
|
967
|
+
.getByRole('combobox');
|
|
968
|
+
await expect(parentPicker).toBeVisible();
|
|
969
|
+
await parentPicker.selectOption({ label: parentName });
|
|
970
|
+
const parentValue = await parentPicker.inputValue();
|
|
971
|
+
expect(parentValue).toBe(String(parentId));
|
|
972
|
+
const childPublication = adminPage
|
|
973
|
+
.getByRole('group', { name: 'Published' })
|
|
974
|
+
.getByRole('combobox');
|
|
975
|
+
await childPublication.selectOption({ label: 'Published' });
|
|
976
|
+
await expect(childPublication).toHaveValue('true');
|
|
977
|
+
|
|
978
|
+
const childCreateResponse = await adminPage.request.post(categoryActionPath, {
|
|
979
|
+
data: { name: childName, parentId: parentValue, published: true },
|
|
980
|
+
headers,
|
|
981
|
+
});
|
|
982
|
+
expect(childCreateResponse.ok()).toBeTruthy();
|
|
983
|
+
childId = (await childCreateResponse.json()).data;
|
|
984
|
+
expectTableIdentity(childId);
|
|
985
|
+
|
|
986
|
+
await adminPage.goto(categoryListPath, { waitUntil: 'load' });
|
|
987
|
+
const childRow = adminPage.getByRole('row', { name: new RegExp(childName) });
|
|
988
|
+
await expect(childRow).toBeVisible();
|
|
989
|
+
await expect(childRow.getByText(parentName, { exact: true })).toBeVisible();
|
|
990
|
+
await expect(childRow.getByText('Published', { exact: true })).toBeVisible();
|
|
991
|
+
|
|
992
|
+
await adminPage.goto(`${categoryListPath}/${childId}`, { waitUntil: 'load' });
|
|
993
|
+
await expect(
|
|
994
|
+
adminPage.getByRole('group', { name: 'Parent category' }).getByRole('textbox'),
|
|
995
|
+
).toHaveValue(parentName);
|
|
996
|
+
await expect(
|
|
997
|
+
adminPage.getByRole('group', { name: 'Published' }).getByRole('textbox'),
|
|
998
|
+
).toHaveValue('Published');
|
|
999
|
+
await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
|
|
1000
|
+
expect(adminPageErrors).toEqual([]);
|
|
1001
|
+
} finally {
|
|
1002
|
+
if (childId && headers) {
|
|
1003
|
+
const response = await adminPage.request.delete(`${categoryActionPath}/${childId}`, {
|
|
1004
|
+
headers,
|
|
1005
|
+
});
|
|
1006
|
+
expect(response.ok()).toBeTruthy();
|
|
1007
|
+
}
|
|
1008
|
+
if (parentId && headers) {
|
|
1009
|
+
const response = await adminPage.request.delete(`${categoryActionPath}/${parentId}`, {
|
|
1010
|
+
headers,
|
|
1011
|
+
});
|
|
1012
|
+
expect(response.ok()).toBeTruthy();
|
|
1013
|
+
}
|
|
1014
|
+
await adminContext.close().catch(() => {});
|
|
1015
|
+
}
|
|
1016
|
+
},
|
|
1017
|
+
);
|
|
1018
|
+
|
|
888
1019
|
test(
|
|
889
1020
|
'Commerce Cart: anonymous browser is redirected to login',
|
|
890
1021
|
{ tag: ['@web', '@cart'] },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.84",
|
|
4
4
|
"gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
|
|
5
5
|
"description": "Vona is an intuitive, elegant and powerful Node.js framework for rapidly developing enterprise applications of any size",
|
|
6
6
|
"keywords": [
|
|
@@ -21,7 +21,7 @@ export class ErrorClass extends BeanSimple {
|
|
|
21
21
|
fail(module, code, ...args) {
|
|
22
22
|
const body = this.parseFail(module, code, ...args);
|
|
23
23
|
|
|
24
|
-
this.ctx.response.status =
|
|
24
|
+
this.ctx.response.status = body.status;
|
|
25
25
|
this.ctx.response.type = 'application/json';
|
|
26
26
|
this.ctx.response.body = { code: body.code, message: body.message }; // body maybe Error
|
|
27
27
|
}
|
|
@@ -32,7 +32,7 @@ export class ErrorClass extends BeanSimple {
|
|
|
32
32
|
const err = new Error();
|
|
33
33
|
err.code = body.code;
|
|
34
34
|
err.message = body.message;
|
|
35
|
-
err.status =
|
|
35
|
+
err.status = body.status;
|
|
36
36
|
throw err;
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -58,9 +58,16 @@ export class ErrorClass extends BeanSimple {
|
|
|
58
58
|
|
|
59
59
|
// convert from enum
|
|
60
60
|
let text;
|
|
61
|
+
let status;
|
|
61
62
|
if (ebError && code && typeof code === 'string') {
|
|
62
63
|
text = code;
|
|
63
|
-
|
|
64
|
+
const declaration = ebError[code];
|
|
65
|
+
if (__isErrorDescriptor(declaration)) {
|
|
66
|
+
code = declaration.code;
|
|
67
|
+
status = declaration.status;
|
|
68
|
+
} else {
|
|
69
|
+
code = declaration;
|
|
70
|
+
}
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
if (code === undefined || code === null || code === '') {
|
|
@@ -75,10 +82,20 @@ export class ErrorClass extends BeanSimple {
|
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
code = __combineErrorCode(module, code);
|
|
78
|
-
|
|
85
|
+
status ??= __calcStatus(code);
|
|
86
|
+
return { code, status, message };
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
89
|
|
|
90
|
+
function __isErrorDescriptor(value) {
|
|
91
|
+
return (
|
|
92
|
+
value &&
|
|
93
|
+
typeof value === 'object' &&
|
|
94
|
+
typeof value.code === 'number' &&
|
|
95
|
+
typeof value.status === 'number'
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
82
99
|
function __combineErrorCode(module, code) {
|
|
83
100
|
if (typeof code !== 'number' || code <= 1000) return code;
|
|
84
101
|
return module ? `${module}:${code}` : code;
|
|
@@ -29,12 +29,14 @@ export type TypeBeanScopeLocaleKeys = keyof IBeanScopeLocale;
|
|
|
29
29
|
export interface IBeanScopeErrors {}
|
|
30
30
|
export type TypeBeanScopeErrorsKeys = keyof IBeanScopeErrors;
|
|
31
31
|
|
|
32
|
+
type TypeErrorDeclarationCode<T> = T extends { code: infer Code } ? Code : T;
|
|
33
|
+
|
|
32
34
|
export type TypeScopesErrorsHelper<
|
|
33
35
|
ModuleName extends keyof IBeanScopeErrors,
|
|
34
36
|
Errors extends IBeanScopeErrors[ModuleName],
|
|
35
37
|
> = {
|
|
36
38
|
// @ts-ignore: ignore
|
|
37
|
-
[K in keyof Errors as `${ModuleName}:${Errors[K]}`]: K;
|
|
39
|
+
[K in keyof Errors as `${ModuleName}:${TypeErrorDeclarationCode<Errors[K]>}`]: K;
|
|
38
40
|
};
|
|
39
41
|
export type TypeScopesErrorCodes = TypeRecordValues<{
|
|
40
42
|
[ModuleName in keyof IBeanScopeErrors]: keyof TypeScopesErrorsHelper<
|
|
@@ -4,7 +4,12 @@ import type { IModuleMain, IMonkeyModule, IMonkeySystem } from './monkey.ts';
|
|
|
4
4
|
|
|
5
5
|
export type TypeModuleResourceLocales = Record<string, object>;
|
|
6
6
|
export type TypeModuleResourceLocaleModules = Record<string, TypeModuleResourceLocales>;
|
|
7
|
-
export
|
|
7
|
+
export interface IModuleResourceErrorDescriptor {
|
|
8
|
+
code: number;
|
|
9
|
+
status: number;
|
|
10
|
+
}
|
|
11
|
+
export type TypeModuleResourceError = number | IModuleResourceErrorDescriptor;
|
|
12
|
+
export type TypeModuleResourceErrors = Record<string, TypeModuleResourceError>;
|
|
8
13
|
export type TypeModuleResourceErrorModules = Record<string, TypeModuleResourceErrors>;
|
|
9
14
|
export type TypeModuleResourceConfig = (app: VonaApplication) => object | Promise<object>;
|
|
10
15
|
|
package/vona/pnpm-lock.yaml
CHANGED
|
@@ -1834,16 +1834,16 @@ importers:
|
|
|
1834
1834
|
src/suite-vendor/a-pay:
|
|
1835
1835
|
dependencies:
|
|
1836
1836
|
vona-module-a-pay:
|
|
1837
|
-
specifier: ^5.0.
|
|
1837
|
+
specifier: ^5.0.3
|
|
1838
1838
|
version: link:modules/a-pay
|
|
1839
1839
|
vona-module-pay-mock:
|
|
1840
|
-
specifier: ^5.0.
|
|
1840
|
+
specifier: ^5.0.3
|
|
1841
1841
|
version: link:modules/pay-mock
|
|
1842
1842
|
vona-module-pay-paypal:
|
|
1843
|
-
specifier: ^5.0.
|
|
1843
|
+
specifier: ^5.0.3
|
|
1844
1844
|
version: link:modules/pay-paypal
|
|
1845
1845
|
vona-module-pay-stripe:
|
|
1846
|
-
specifier: ^5.0.
|
|
1846
|
+
specifier: ^5.0.3
|
|
1847
1847
|
version: link:modules/pay-stripe
|
|
1848
1848
|
|
|
1849
1849
|
src/suite-vendor/a-pay/modules/a-pay:
|
|
@@ -1922,7 +1922,7 @@ importers:
|
|
|
1922
1922
|
specifier: ^5.0.8
|
|
1923
1923
|
version: link:modules/test-file
|
|
1924
1924
|
vona-module-test-pay:
|
|
1925
|
-
specifier: ^5.0.
|
|
1925
|
+
specifier: ^5.0.2
|
|
1926
1926
|
version: link:modules/test-pay
|
|
1927
1927
|
|
|
1928
1928
|
src/suite-vendor/a-test/modules/test-auth:
|
|
@@ -142,7 +142,12 @@ declare module 'vona' {
|
|
|
142
142
|
import type { IModelGetOptions, IModelMethodOptions, IModelSelectParams, TypeModelSelectAndCount, TypeModelRelationResult, TypeModelWhere, IModelInsertOptions, TypeModelMutateRelationData, IModelDeleteOptions, IModelUpdateOptions, IModelMutateOptions, IModelSelectCountParams, IModelIncrementParams, IModelSelectAggrParams, TypeModelAggrRelationResult, IModelSelectGroupParams, TypeModelGroupRelationResult } from 'vona-module-a-orm';
|
|
143
143
|
import { SymbolKeyEntity, SymbolKeyEntityMeta, SymbolKeyModelOptions } from 'vona-module-a-orm';
|
|
144
144
|
declare module 'vona-module-commerce-catalog' {
|
|
145
|
-
export interface
|
|
145
|
+
export interface IModelOptionsCategory {
|
|
146
|
+
relations: {
|
|
147
|
+
parent: IModelRelationBelongsTo<'commerce-catalog:category', 'commerce-catalog:category', false, 'id'|'name'>;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
export interface IModelOptionsProduct {
|
|
146
151
|
relations: {
|
|
147
152
|
category: IModelRelationBelongsTo<'commerce-catalog:product', 'commerce-catalog:category', false, '*'>;
|
|
148
153
|
skus: IModelRelationHasMany<'commerce-catalog:sku', 'productId', false, '*', undefined, undefined, undefined>;
|
|
@@ -159,7 +164,17 @@ export interface IModelOptionsSku {
|
|
|
159
164
|
[SymbolKeyEntityMeta]: EntityCategoryMeta;
|
|
160
165
|
[SymbolKeyModelOptions]: IModelOptionsCategory;
|
|
161
166
|
get<T extends IModelGetOptions<EntityCategory,ModelCategory>>(where: TypeModelWhere<EntityCategory>, options?: T): Promise<TypeModelRelationResult<EntityCategory, ModelCategory, T> | undefined>;
|
|
167
|
+
/**
|
|
168
|
+
* Retrieves one matching primary row with a pessimistic FOR UPDATE lock.
|
|
169
|
+
* Requires an active transaction. The lock is released when that transaction completes.
|
|
170
|
+
* Entity and query caches are bypassed.
|
|
171
|
+
*/
|
|
162
172
|
getForUpdate<T extends IModelGetOptions<EntityCategory,ModelCategory>>(where: TypeModelWhere<EntityCategory>, options?: T): Promise<TypeModelRelationResult<EntityCategory, ModelCategory, T> | undefined>;
|
|
173
|
+
/**
|
|
174
|
+
* Retrieves a primary row by ID with the same pessimistic FOR UPDATE lock semantics.
|
|
175
|
+
* Requires an active transaction. The lock is released when that transaction completes.
|
|
176
|
+
* Entity and query caches are bypassed.
|
|
177
|
+
*/
|
|
163
178
|
getByIdForUpdate<T extends IModelGetOptions<EntityCategory,ModelCategory>>(id: TableIdentity, options?: T): Promise<TypeModelRelationResult<EntityCategory, ModelCategory, T> | undefined>;
|
|
164
179
|
mget<T extends IModelGetOptions<EntityCategory,ModelCategory>>(ids: TableIdentity[], options?: T): Promise<TypeModelRelationResult<EntityCategory, ModelCategory, T>[]>;
|
|
165
180
|
selectAndCount<T extends IModelSelectParams<EntityCategory,ModelCategory,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<TypeModelSelectAndCount<EntityCategory, ModelCategory, T>>;
|
|
@@ -190,7 +205,17 @@ export interface ModelProduct {
|
|
|
190
205
|
[SymbolKeyEntityMeta]: EntityProductMeta;
|
|
191
206
|
[SymbolKeyModelOptions]: IModelOptionsProduct;
|
|
192
207
|
get<T extends IModelGetOptions<EntityProduct,ModelProduct>>(where: TypeModelWhere<EntityProduct>, options?: T): Promise<TypeModelRelationResult<EntityProduct, ModelProduct, T> | undefined>;
|
|
208
|
+
/**
|
|
209
|
+
* Retrieves one matching primary row with a pessimistic FOR UPDATE lock.
|
|
210
|
+
* Requires an active transaction. The lock is released when that transaction completes.
|
|
211
|
+
* Entity and query caches are bypassed.
|
|
212
|
+
*/
|
|
193
213
|
getForUpdate<T extends IModelGetOptions<EntityProduct,ModelProduct>>(where: TypeModelWhere<EntityProduct>, options?: T): Promise<TypeModelRelationResult<EntityProduct, ModelProduct, T> | undefined>;
|
|
214
|
+
/**
|
|
215
|
+
* Retrieves a primary row by ID with the same pessimistic FOR UPDATE lock semantics.
|
|
216
|
+
* Requires an active transaction. The lock is released when that transaction completes.
|
|
217
|
+
* Entity and query caches are bypassed.
|
|
218
|
+
*/
|
|
194
219
|
getByIdForUpdate<T extends IModelGetOptions<EntityProduct,ModelProduct>>(id: TableIdentity, options?: T): Promise<TypeModelRelationResult<EntityProduct, ModelProduct, T> | undefined>;
|
|
195
220
|
mget<T extends IModelGetOptions<EntityProduct,ModelProduct>>(ids: TableIdentity[], options?: T): Promise<TypeModelRelationResult<EntityProduct, ModelProduct, T>[]>;
|
|
196
221
|
selectAndCount<T extends IModelSelectParams<EntityProduct,ModelProduct,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<TypeModelSelectAndCount<EntityProduct, ModelProduct, T>>;
|
|
@@ -217,7 +242,17 @@ export interface ModelSku {
|
|
|
217
242
|
[SymbolKeyEntityMeta]: EntitySkuMeta;
|
|
218
243
|
[SymbolKeyModelOptions]: IModelOptionsSku;
|
|
219
244
|
get<T extends IModelGetOptions<EntitySku,ModelSku>>(where: TypeModelWhere<EntitySku>, options?: T): Promise<TypeModelRelationResult<EntitySku, ModelSku, T> | undefined>;
|
|
245
|
+
/**
|
|
246
|
+
* Retrieves one matching primary row with a pessimistic FOR UPDATE lock.
|
|
247
|
+
* Requires an active transaction. The lock is released when that transaction completes.
|
|
248
|
+
* Entity and query caches are bypassed.
|
|
249
|
+
*/
|
|
220
250
|
getForUpdate<T extends IModelGetOptions<EntitySku,ModelSku>>(where: TypeModelWhere<EntitySku>, options?: T): Promise<TypeModelRelationResult<EntitySku, ModelSku, T> | undefined>;
|
|
251
|
+
/**
|
|
252
|
+
* Retrieves a primary row by ID with the same pessimistic FOR UPDATE lock semantics.
|
|
253
|
+
* Requires an active transaction. The lock is released when that transaction completes.
|
|
254
|
+
* Entity and query caches are bypassed.
|
|
255
|
+
*/
|
|
221
256
|
getByIdForUpdate<T extends IModelGetOptions<EntitySku,ModelSku>>(id: TableIdentity, options?: T): Promise<TypeModelRelationResult<EntitySku, ModelSku, T> | undefined>;
|
|
222
257
|
mget<T extends IModelGetOptions<EntitySku,ModelSku>>(ids: TableIdentity[], options?: T): Promise<TypeModelRelationResult<EntitySku, ModelSku, T>[]>;
|
|
223
258
|
selectAndCount<T extends IModelSelectParams<EntitySku,ModelSku,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<TypeModelSelectAndCount<EntitySku, ModelSku, T>>;
|
package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categorySelectResItem.tsx
CHANGED
|
@@ -46,7 +46,9 @@ export interface IDtoOptionsCategorySelectResItem extends IDecoratorDtoOptions {
|
|
|
46
46
|
}),
|
|
47
47
|
],
|
|
48
48
|
})
|
|
49
|
-
export class DtoCategorySelectResItem extends $Dto.get(() => ModelCategory
|
|
49
|
+
export class DtoCategorySelectResItem extends $Dto.get(() => ModelCategory, {
|
|
50
|
+
include: { parent: true },
|
|
51
|
+
}) {
|
|
50
52
|
@Api.field(
|
|
51
53
|
v.title($locale('Operations')),
|
|
52
54
|
ZovaRender.order(1, 'max'),
|
|
@@ -22,4 +22,6 @@ export interface IDtoOptionsCategoryView extends IDecoratorDtoOptions {}
|
|
|
22
22
|
}),
|
|
23
23
|
],
|
|
24
24
|
})
|
|
25
|
-
export class DtoCategoryView extends $Dto.get(() => ModelCategory
|
|
25
|
+
export class DtoCategoryView extends $Dto.get(() => ModelCategory, {
|
|
26
|
+
include: { parent: true },
|
|
27
|
+
}) {}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TableIdentity } from 'table-identity';
|
|
2
2
|
import type { IDecoratorEntityOptions } from 'vona-module-a-orm';
|
|
3
3
|
|
|
4
|
-
import { $makeMetadata, Api, v } from 'vona-module-a-openapiutils';
|
|
4
|
+
import { $makeMetadata, $resourceName, Api, v } from 'vona-module-a-openapiutils';
|
|
5
5
|
import { Entity, EntityBase } from 'vona-module-a-orm';
|
|
6
6
|
import { ZovaRender } from 'zova-rest-cabloy-basic-admin';
|
|
7
7
|
|
|
@@ -9,6 +9,11 @@ import { $locale } from '../.metadata/locales.ts';
|
|
|
9
9
|
|
|
10
10
|
export interface IEntityOptionsCategory extends IDecoratorEntityOptions {}
|
|
11
11
|
|
|
12
|
+
export const categoryPublicationItems = [
|
|
13
|
+
{ value: false, title: $locale('Unpublished') },
|
|
14
|
+
{ value: true, title: $locale('Published') },
|
|
15
|
+
];
|
|
16
|
+
|
|
12
17
|
@Entity<IEntityOptionsCategory>('commerceCatalogCategory', {
|
|
13
18
|
openapi: { title: $locale('Category') },
|
|
14
19
|
fields: {
|
|
@@ -41,11 +46,25 @@ export class EntityCategory extends EntityBase {
|
|
|
41
46
|
v.title($locale('ParentCategory')),
|
|
42
47
|
v.optional(),
|
|
43
48
|
ZovaRender.order(2),
|
|
49
|
+
ZovaRender.field('basic-resource:formFieldResourcePicker', {
|
|
50
|
+
resource: $resourceName('commerce-catalog:category'),
|
|
51
|
+
relationName: 'parent',
|
|
52
|
+
}),
|
|
53
|
+
ZovaRender.cell('basic-resource:resourcePicker', {
|
|
54
|
+
resource: $resourceName('commerce-catalog:category'),
|
|
55
|
+
relationName: 'parent',
|
|
56
|
+
}),
|
|
44
57
|
v.tableIdentity(),
|
|
45
58
|
)
|
|
46
59
|
parentId?: TableIdentity;
|
|
47
60
|
|
|
48
|
-
@Api.field(
|
|
61
|
+
@Api.field(
|
|
62
|
+
v.title($locale('Published')),
|
|
63
|
+
v.default(false),
|
|
64
|
+
ZovaRender.order(3),
|
|
65
|
+
ZovaRender.field('basic-select:formFieldSelect', { items: categoryPublicationItems }),
|
|
66
|
+
ZovaRender.cell('basic-select:select', { items: categoryPublicationItems }),
|
|
67
|
+
)
|
|
49
68
|
published: boolean;
|
|
50
69
|
|
|
51
70
|
@Api.field(v.title($locale('Description')), v.optional(), ZovaRender.order(4))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IDecoratorModelOptions } from 'vona-module-a-orm';
|
|
2
2
|
|
|
3
|
-
import { BeanModelBase, Model } from 'vona-module-a-orm';
|
|
3
|
+
import { $relation, BeanModelBase, Model } from 'vona-module-a-orm';
|
|
4
4
|
|
|
5
5
|
import { EntityCategory } from '../entity/category.tsx';
|
|
6
6
|
|
|
@@ -8,6 +8,16 @@ export interface IModelOptionsCategory extends IDecoratorModelOptions<EntityCate
|
|
|
8
8
|
|
|
9
9
|
@Model<IModelOptionsCategory>({
|
|
10
10
|
entity: EntityCategory,
|
|
11
|
+
relations: {
|
|
12
|
+
parent: $relation.belongsTo(
|
|
13
|
+
'commerce-catalog:category',
|
|
14
|
+
'commerce-catalog:category',
|
|
15
|
+
'parentId',
|
|
16
|
+
{
|
|
17
|
+
columns: ['id', 'name'],
|
|
18
|
+
},
|
|
19
|
+
),
|
|
20
|
+
},
|
|
11
21
|
cache: {
|
|
12
22
|
modelsClear: 'commerce-catalog:product',
|
|
13
23
|
},
|
|
@@ -19,11 +19,16 @@ export class ServiceCategory extends BeanBase {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
async select(params?: IQueryParams<ModelCategory>): Promise<DtoCategorySelectRes> {
|
|
22
|
-
return await this.scope.model.category.selectAndCount(
|
|
22
|
+
return await this.scope.model.category.selectAndCount({
|
|
23
|
+
...params,
|
|
24
|
+
include: { parent: true },
|
|
25
|
+
});
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
async view(id: TableIdentity): Promise<DtoCategoryView | undefined> {
|
|
26
|
-
return await this.scope.model.category.getById(id
|
|
29
|
+
return await this.scope.model.category.getById(id, {
|
|
30
|
+
include: { parent: true },
|
|
31
|
+
});
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
async update(id: TableIdentity, category: DtoCategoryUpdate) {
|