@goplusvn/core 0.1.71 → 0.1.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/auth/index.ts +5 -1
- package/src/branding/__tests__/identity.test.ts +101 -0
- package/src/branding/__tests__/logo-route.test.ts +143 -0
- package/src/branding/identity.ts +74 -0
- package/src/branding/index.ts +36 -0
- package/src/branding/logo-route.ts +249 -0
- package/src/branding/pwa.tsx +146 -0
- package/src/branding/types.ts +45 -0
- package/src/configs/entities/departments.config.ts +1 -0
- package/src/configs/entities/material-categories.config.ts +1 -0
- package/src/crud/components/crud-export-button.tsx +3 -2
- package/src/crud/components/crud-page.tsx +56 -25
- package/src/crud/components/crud-row-actions.tsx +16 -4
- package/src/crud/crud-route-handlers.test.ts +352 -0
- package/src/crud/crud-route-handlers.ts +260 -13
- package/src/crud/index.ts +3 -0
- package/src/crud/lib/coerce.test.ts +118 -0
- package/src/crud/lib/coerce.ts +95 -0
- package/src/crud/lib/crud-utils.ts +4 -0
- package/src/crud/lib/entity-endpoints.test.ts +41 -0
- package/src/crud/lib/entity-endpoints.ts +23 -0
- package/src/crud/lib/errors.ts +14 -0
- package/src/crud/lib/import-request.ts +99 -0
- package/src/crud/lib/mutation-builder.test.ts +114 -0
- package/src/crud/lib/mutation-builder.ts +27 -34
- package/src/crud/lib/permissions.test.ts +57 -0
- package/src/crud/lib/permissions.ts +25 -13
- package/src/crud/lib/query-builder.ts +13 -6
- package/src/crud/pages/entity-crud-page.tsx +11 -8
- package/src/crud/server-service.test.ts +112 -0
- package/src/crud/server-service.ts +52 -27
- package/src/crud/server.ts +11 -1
- package/src/guardrails/rules/design.ts +3 -2
- package/src/styles/base.css +16 -0
- package/src/types/index.ts +75 -10
- package/src/ui/data-display/__tests__/use-client-pagination.test.ts +59 -0
- package/src/ui/data-display/data-table/__tests__/data-table-row-memo.test.tsx +96 -0
- package/src/ui/data-display/data-table/data-table-context.tsx +5 -0
- package/src/ui/data-display/data-table/data-table.tsx +68 -23
- package/src/ui/data-display/data-table-pagination.tsx +25 -8
- package/src/ui/data-display/index.tsx +1 -0
- package/src/ui/data-display/use-client-pagination.ts +48 -0
- package/src/ui/errors/error-view.tsx +80 -87
- package/src/ui/errors/index.ts +6 -1
- package/src/ui/errors/not-found-view.tsx +89 -16
- package/src/ui/errors/status-page.tsx +132 -0
- package/src/ui/layout/logo.tsx +5 -0
- package/src/ui/pages/not-found.tsx +11 -27
- package/templates/starter-app/AGENTS.md +1 -1
- package/templates/starter-app/next.config.mjs +8 -0
- package/templates/starter-app/package.json +1 -0
- package/templates/starter-app/prisma/migrations/20260807115347_platform_pages/migration.sql +87 -0
- package/templates/starter-app/prisma/migrations/20260808003811_company_profile/migration.sql +25 -0
- package/templates/starter-app/public/images/illustrations/characters/character-01.svg +33 -0
- package/templates/starter-app/public/images/illustrations/characters/character-02.svg +30 -0
- package/templates/starter-app/public/images/illustrations/characters/character-03.svg +30 -0
- package/templates/starter-app/public/images/illustrations/characters/character-04.svg +27 -0
- package/templates/starter-app/public/images/logos/goeat_logo.png +0 -0
- package/templates/starter-app/scripts/rbac-sync.ts +49 -21
- package/templates/starter-app/src/__tests__/architecture.test.ts +3 -2
- package/templates/starter-app/src/app/[lang]/(main)/actions/page.tsx +4 -4
- package/templates/starter-app/src/app/[lang]/(main)/admin/system/cache/page.tsx +1 -2
- package/templates/starter-app/src/app/[lang]/(main)/admin/system/company/actions.ts +7 -5
- package/templates/starter-app/src/app/[lang]/(main)/admin/system/company/company-profile-form.tsx +38 -11
- package/templates/starter-app/src/app/[lang]/(main)/admin/system/company/page.tsx +2 -1
- package/templates/starter-app/src/app/[lang]/(main)/crud/[entity]/page.tsx +4 -2
- package/templates/starter-app/src/app/[lang]/(main)/layout.tsx +2 -1
- package/templates/starter-app/src/app/[lang]/(main)/page.tsx +4 -4
- package/templates/starter-app/src/app/[lang]/(main)/resources/page.tsx +5 -5
- package/templates/starter-app/src/app/[lang]/(main)/roles/[id]/edit/page.tsx +5 -2
- package/templates/starter-app/src/app/[lang]/(main)/roles/new/page.tsx +1 -0
- package/templates/starter-app/src/app/[lang]/(main)/roles/page.tsx +1 -0
- package/templates/starter-app/src/app/[lang]/(main)/user/profile/actions.ts +4 -2
- package/templates/starter-app/src/app/[lang]/(main)/user/profile/page.tsx +3 -1
- package/templates/starter-app/src/app/[lang]/(main)/user/profile/profile-client-page.tsx +13 -5
- package/templates/starter-app/src/app/[lang]/(main)/users/page.tsx +3 -2
- package/templates/starter-app/src/app/[lang]/layout.tsx +27 -2
- package/templates/starter-app/src/app/api/admin/system/audit/route.ts +4 -3
- package/templates/starter-app/src/app/api/admin/system/jobs/[name]/history/route.ts +1 -1
- package/templates/starter-app/src/app/api/admin/system/jobs/route.ts +31 -12
- package/templates/starter-app/src/app/api/admin/system/settings/all/route.ts +2 -2
- package/templates/starter-app/src/app/api/admin/system/settings/create/route.ts +10 -3
- package/templates/starter-app/src/app/api/admin/system/settings/delete/route.ts +10 -3
- package/templates/starter-app/src/app/api/admin/system/settings/route.ts +14 -5
- package/templates/starter-app/src/app/api/admin/system/settings/toggle-status/route.ts +16 -7
- package/templates/starter-app/src/app/api/admin/system/settings/update/route.ts +12 -6
- package/templates/starter-app/src/app/api/admin/system/settings/update-full/route.ts +12 -6
- package/templates/starter-app/src/app/api/better-auth/[...all]/route.ts +1 -1
- package/templates/starter-app/src/app/api/branding/logo/route.ts +25 -0
- package/templates/starter-app/src/app/api/crud/[entity]/[id]/route.ts +2 -2
- package/templates/starter-app/src/app/api/crud/[entity]/export/route.ts +16 -0
- package/templates/starter-app/src/app/api/crud/[entity]/import/route.ts +17 -0
- package/templates/starter-app/src/app/api/crud/[entity]/route.ts +2 -2
- package/templates/starter-app/src/app/api/error-logs/[id]/route.ts +4 -2
- package/templates/starter-app/src/app/api/error-logs/route.ts +26 -19
- package/templates/starter-app/src/app/api/files/[...key]/route.ts +1 -2
- package/templates/starter-app/src/app/api/job-titles/route.ts +1 -1
- package/templates/starter-app/src/app/api/notifications/read/route.ts +1 -1
- package/templates/starter-app/src/app/api/notifications/route.ts +1 -1
- package/templates/starter-app/src/app/api/notifications/unread-count/route.ts +1 -1
- package/templates/starter-app/src/app/api/rbac/permissions-version/route.ts +4 -1
- package/templates/starter-app/src/app/api/roles/[id]/route.ts +2 -1
- package/templates/starter-app/src/app/api/roles/route.ts +2 -1
- package/templates/starter-app/src/app/api/security/sessions/[id]/route.ts +5 -5
- package/templates/starter-app/src/app/api/security/sessions/revoke-user/route.ts +1 -1
- package/templates/starter-app/src/app/api/security/sessions/route.ts +2 -2
- package/templates/starter-app/src/app/api/system-categories/route.ts +39 -14
- package/templates/starter-app/src/app/api/system-category-groups/route.ts +32 -14
- package/templates/starter-app/src/app/api/tasks/[id]/download/route.ts +7 -3
- package/templates/starter-app/src/app/api/tasks/route.ts +5 -2
- package/templates/starter-app/src/app/api/upload/route.ts +1 -2
- package/templates/starter-app/src/app/api/users/[id]/route.ts +20 -12
- package/templates/starter-app/src/app/api/users/route.ts +11 -9
- package/templates/starter-app/src/app/globals.css +0 -1
- package/templates/starter-app/src/app/layout.tsx +42 -1
- package/templates/starter-app/src/app/manifest.ts +23 -19
- package/templates/starter-app/src/components/layout/main-layout-wrapper.tsx +5 -4
- package/templates/starter-app/src/configs/entities/department.config.ts +7 -4
- package/templates/starter-app/src/configs/entities/index.ts +1 -1
- package/templates/starter-app/src/configs/entities/job-titles.config.ts +2 -0
- package/templates/starter-app/src/configs/entities/system-alerts.config.ts +2 -0
- package/templates/starter-app/src/configs/entities/users.config.ts +15 -2
- package/templates/starter-app/src/configs/i18n.ts +6 -6
- package/templates/starter-app/src/configs/permissions/menu-tree.ts +1 -1
- package/templates/starter-app/src/configs/tenant.ts +6 -1
- package/templates/starter-app/src/data/dictionary.ts +2 -1
- package/templates/starter-app/src/data/navigations.ts +12 -2
- package/templates/starter-app/src/lib/action-guard.ts +3 -4
- package/templates/starter-app/src/lib/api-handler.ts +27 -19
- package/templates/starter-app/src/lib/auth.ts +3 -1
- package/templates/starter-app/src/lib/branch-scope.ts +1 -1
- package/templates/starter-app/src/lib/crud/index.ts +5 -12
- package/templates/starter-app/src/lib/errors/log-server-error.ts +5 -4
- package/templates/starter-app/src/lib/logger.ts +11 -4
- package/templates/starter-app/src/lib/page-guard.ts +2 -3
- package/templates/starter-app/src/lib/prisma.ts +13 -5
- package/templates/starter-app/src/lib/rbac/access.ts +2 -2
- package/templates/starter-app/src/lib/storage.ts +3 -1
- package/templates/starter-app/src/providers/index.tsx +13 -2
- package/templates/starter-app/src/providers/theme-provider.tsx +4 -3
- package/templates/starter-app/src/proxy.ts +3 -1
- package/templates/starter-app/src/server/services/company-service.ts +4 -2
- package/templates/starter-app/src/server/services/notification-service.ts +6 -6
- package/templates/starter-app/src/server/services/system-config-service.ts +21 -9
- package/templates/starter-app/src/server/services/user-service.ts +2 -2
- package/templates/starter-app/src/server/tasks/handlers/export-departments.ts +15 -9
- package/templates/starter-app/src/server/tasks/task-runner.ts +6 -7
- package/templates/starter-app/tsconfig.json +8 -0
- package/templates/starter-app/vitest.config.ts +17 -1
|
@@ -18,7 +18,7 @@ const MASK = "••••••••"
|
|
|
18
18
|
export class ConfigError extends Error {
|
|
19
19
|
constructor(
|
|
20
20
|
message: string,
|
|
21
|
-
readonly status: number
|
|
21
|
+
readonly status: number
|
|
22
22
|
) {
|
|
23
23
|
super(message)
|
|
24
24
|
}
|
|
@@ -29,14 +29,13 @@ export class ConfigError extends Error {
|
|
|
29
29
|
* (khi đó route phải để `serverError` ghi lại stack thật).
|
|
30
30
|
*/
|
|
31
31
|
export function configErrorInfo(
|
|
32
|
-
error: unknown
|
|
32
|
+
error: unknown
|
|
33
33
|
): { message: string; status: number } | null {
|
|
34
34
|
return error instanceof ConfigError
|
|
35
35
|
? { message: error.message, status: error.status }
|
|
36
36
|
: null
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
39
|
const mask = (config: any) => ({
|
|
41
40
|
...config,
|
|
42
41
|
value: config.isEncrypted ? MASK : config.value,
|
|
@@ -77,7 +76,7 @@ export async function createConfig(input: ConfigInput, userId: string) {
|
|
|
77
76
|
if (!/^[a-zA-Z0-9._-]+$/.test(key)) {
|
|
78
77
|
throw new ConfigError(
|
|
79
78
|
"Key chỉ được chứa chữ, số, dấu chấm, gạch dưới và gạch ngang",
|
|
80
|
-
400
|
|
79
|
+
400
|
|
81
80
|
)
|
|
82
81
|
}
|
|
83
82
|
if (await db.systemConfig.findUnique({ where: { key } })) {
|
|
@@ -103,8 +102,13 @@ export async function createConfig(input: ConfigInput, userId: string) {
|
|
|
103
102
|
}
|
|
104
103
|
|
|
105
104
|
/** Đổi MỖI giá trị — đường dùng nhiều nhất trên UI. */
|
|
106
|
-
export async function updateConfigValue(
|
|
107
|
-
|
|
105
|
+
export async function updateConfigValue(
|
|
106
|
+
key: string,
|
|
107
|
+
value: unknown,
|
|
108
|
+
userId: string
|
|
109
|
+
) {
|
|
110
|
+
if (!key || value === undefined)
|
|
111
|
+
throw new ConfigError("Thiếu key hoặc value", 400)
|
|
108
112
|
const existing = await requireConfig(key)
|
|
109
113
|
if (existing.isReadOnly) throw new ConfigError("Cấu hình này chỉ đọc", 403)
|
|
110
114
|
|
|
@@ -128,7 +132,10 @@ export async function updateConfigFull(input: ConfigInput, userId: string) {
|
|
|
128
132
|
value: input.value ?? existing.value,
|
|
129
133
|
type: input.type ?? existing.type,
|
|
130
134
|
category: input.category ?? existing.category,
|
|
131
|
-
description:
|
|
135
|
+
description:
|
|
136
|
+
input.description !== undefined
|
|
137
|
+
? input.description
|
|
138
|
+
: existing.description,
|
|
132
139
|
isEncrypted: input.isEncrypted ?? existing.isEncrypted,
|
|
133
140
|
isReadOnly: input.isReadOnly ?? existing.isReadOnly,
|
|
134
141
|
updatedBy: userId,
|
|
@@ -141,13 +148,18 @@ export async function updateConfigFull(input: ConfigInput, userId: string) {
|
|
|
141
148
|
export async function deleteConfig(key: string) {
|
|
142
149
|
if (!key) throw new ConfigError("Thiếu key", 400)
|
|
143
150
|
const existing = await requireConfig(key)
|
|
144
|
-
if (existing.isReadOnly)
|
|
151
|
+
if (existing.isReadOnly)
|
|
152
|
+
throw new ConfigError("Không thể xóa cấu hình được bảo vệ", 403)
|
|
145
153
|
|
|
146
154
|
await db.systemConfig.delete({ where: { key } })
|
|
147
155
|
invalidate()
|
|
148
156
|
}
|
|
149
157
|
|
|
150
|
-
export async function toggleConfigStatus(
|
|
158
|
+
export async function toggleConfigStatus(
|
|
159
|
+
key: string,
|
|
160
|
+
status: string,
|
|
161
|
+
userId: string
|
|
162
|
+
) {
|
|
151
163
|
if (!key) throw new ConfigError("Thiếu key", 400)
|
|
152
164
|
if (!["active", "inactive"].includes(status)) {
|
|
153
165
|
throw new ConfigError("status phải là 'active' hoặc 'inactive'", 400)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Prisma } from "@prisma/client"
|
|
2
|
+
|
|
1
3
|
import { db } from "@/lib/prisma"
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -21,8 +23,6 @@ export interface UsersListParams {
|
|
|
21
23
|
status?: string
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
import type { Prisma } from "@prisma/client"
|
|
25
|
-
|
|
26
26
|
type Where = Prisma.UserWhereInput
|
|
27
27
|
|
|
28
28
|
export async function getUsersList(params: UsersListParams = {}) {
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* không lộ đường dẫn thật.
|
|
8
8
|
* 3. Ném lỗi thoải mái: engine bắt, ghi status=error kèm message và báo chuông.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
10
|
+
import type { TaskContext, TaskFileResult } from "@goerp/core/tasks"
|
|
11
11
|
|
|
12
12
|
import { db } from "@/lib/prisma"
|
|
13
13
|
|
|
14
|
-
import
|
|
14
|
+
import { registerTaskHandler, saveTaskFile } from "../task-runner"
|
|
15
15
|
|
|
16
16
|
export const EXPORT_DEPARTMENTS_TASK = "export-departments"
|
|
17
17
|
|
|
@@ -28,7 +28,9 @@ registerTaskHandler(
|
|
|
28
28
|
EXPORT_DEPARTMENTS_TASK,
|
|
29
29
|
async ({ setProgress }: TaskContext): Promise<TaskFileResult> => {
|
|
30
30
|
const total = await db.department.count()
|
|
31
|
-
const rows: string[] = [
|
|
31
|
+
const rows: string[] = [
|
|
32
|
+
["Mã", "Tên", "Mô tả", "Thứ tự", "Trạng thái"].join(","),
|
|
33
|
+
]
|
|
32
34
|
|
|
33
35
|
for (let skip = 0; skip < total; skip += PAGE_SIZE) {
|
|
34
36
|
const page = await db.department.findMany({
|
|
@@ -38,21 +40,25 @@ registerTaskHandler(
|
|
|
38
40
|
})
|
|
39
41
|
for (const d of page) {
|
|
40
42
|
rows.push(
|
|
41
|
-
[d.code, d.name, d.description, d.order, d.status]
|
|
43
|
+
[d.code, d.name, d.description, d.order, d.status]
|
|
44
|
+
.map(csvCell)
|
|
45
|
+
.join(",")
|
|
42
46
|
)
|
|
43
47
|
}
|
|
44
|
-
await setProgress(
|
|
48
|
+
await setProgress(
|
|
49
|
+
Math.round(((skip + page.length) / Math.max(total, 1)) * 100)
|
|
50
|
+
)
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
// BOM
|
|
48
|
-
const buffer = Buffer.from(`
|
|
53
|
+
// BOM để Excel bản tiếng Việt không đọc UTF-8 thành ký tự lỗi.
|
|
54
|
+
const buffer = Buffer.from(` ${rows.join("\n")}`, "utf8")
|
|
49
55
|
const fileName = "phong-ban.csv"
|
|
50
56
|
const fileKey = await saveTaskFile(
|
|
51
57
|
buffer,
|
|
52
58
|
`${EXPORT_DEPARTMENTS_TASK}/${fileName}`,
|
|
53
|
-
"text/csv"
|
|
59
|
+
"text/csv"
|
|
54
60
|
)
|
|
55
61
|
|
|
56
62
|
return { fileKey, fileName, contentType: "text/csv", rowCount: total }
|
|
57
|
-
}
|
|
63
|
+
}
|
|
58
64
|
)
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { configureTaskRunner } from "@goerp/core/tasks"
|
|
2
|
+
|
|
3
|
+
import type { TaskDb, TaskNotifyInput } from "@goerp/core/tasks"
|
|
4
|
+
|
|
5
|
+
import { db } from "@/lib/prisma"
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
8
|
* Tác vụ nền — engine ở @goerp/core/tasks (bảng `background_tasks` ship qua
|
|
3
9
|
* `goerp-features sync`). Hàng đợi nằm trong DB, worker chạy NGAY trong tiến
|
|
@@ -7,13 +13,6 @@
|
|
|
7
13
|
* Đừng import file này ở nơi khác — import `@/server/tasks` để các handler
|
|
8
14
|
* được đăng ký trước khi có ai enqueue.
|
|
9
15
|
*/
|
|
10
|
-
import {
|
|
11
|
-
configureTaskRunner,
|
|
12
|
-
type TaskDb,
|
|
13
|
-
type TaskNotifyInput,
|
|
14
|
-
} from "@goerp/core/tasks"
|
|
15
|
-
|
|
16
|
-
import { db } from "@/lib/prisma"
|
|
17
16
|
|
|
18
17
|
configureTaskRunner({
|
|
19
18
|
db: db as unknown as TaskDb,
|
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
"../../src",
|
|
31
31
|
"./node_modules/@goerp/core/src"
|
|
32
32
|
],
|
|
33
|
+
"@goerp/core/user/client-page": [
|
|
34
|
+
"../../src/user/pages/users-client-page.tsx",
|
|
35
|
+
"./node_modules/@goerp/core/src/user/pages/users-client-page.tsx"
|
|
36
|
+
],
|
|
37
|
+
"@goerp/core/tasks/ui": [
|
|
38
|
+
"../../src/tasks/ui/task-list-client.tsx",
|
|
39
|
+
"./node_modules/@goerp/core/src/tasks/ui/task-list-client.tsx"
|
|
40
|
+
],
|
|
33
41
|
"@goerp/core/*": [
|
|
34
42
|
"../../src/*",
|
|
35
43
|
"./node_modules/@goerp/core/src/*"
|
|
@@ -12,6 +12,22 @@ export default defineConfig({
|
|
|
12
12
|
},
|
|
13
13
|
resolve: {
|
|
14
14
|
// Phải khai lại alias của tsconfig: vitest không đọc tsconfig paths.
|
|
15
|
-
|
|
15
|
+
// @goerp/core trỏ THẲNG src của core local (../../src trong repo core) —
|
|
16
|
+
// thiếu là guardrails không resolve được, hoặc rơi về bản core CŨ trong
|
|
17
|
+
// node_modules → mixed-type khi core local đổi type. Hai subpath
|
|
18
|
+
// user/client-page và tasks/ui exports-map sang file không trùng đường
|
|
19
|
+
// dẫn src nên phải khai riêng, y như tsconfig.
|
|
20
|
+
alias: {
|
|
21
|
+
"@": path.resolve(__dirname, "./src"),
|
|
22
|
+
"@goerp/core/user/client-page": path.resolve(
|
|
23
|
+
__dirname,
|
|
24
|
+
"../../src/user/pages/users-client-page.tsx",
|
|
25
|
+
),
|
|
26
|
+
"@goerp/core/tasks/ui": path.resolve(
|
|
27
|
+
__dirname,
|
|
28
|
+
"../../src/tasks/ui/task-list-client.tsx",
|
|
29
|
+
),
|
|
30
|
+
"@goerp/core": path.resolve(__dirname, "../../src"),
|
|
31
|
+
},
|
|
16
32
|
},
|
|
17
33
|
})
|