@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.
Files changed (150) hide show
  1. package/package.json +2 -1
  2. package/src/auth/index.ts +5 -1
  3. package/src/branding/__tests__/identity.test.ts +101 -0
  4. package/src/branding/__tests__/logo-route.test.ts +143 -0
  5. package/src/branding/identity.ts +74 -0
  6. package/src/branding/index.ts +36 -0
  7. package/src/branding/logo-route.ts +249 -0
  8. package/src/branding/pwa.tsx +146 -0
  9. package/src/branding/types.ts +45 -0
  10. package/src/configs/entities/departments.config.ts +1 -0
  11. package/src/configs/entities/material-categories.config.ts +1 -0
  12. package/src/crud/components/crud-export-button.tsx +3 -2
  13. package/src/crud/components/crud-page.tsx +56 -25
  14. package/src/crud/components/crud-row-actions.tsx +16 -4
  15. package/src/crud/crud-route-handlers.test.ts +352 -0
  16. package/src/crud/crud-route-handlers.ts +260 -13
  17. package/src/crud/index.ts +3 -0
  18. package/src/crud/lib/coerce.test.ts +118 -0
  19. package/src/crud/lib/coerce.ts +95 -0
  20. package/src/crud/lib/crud-utils.ts +4 -0
  21. package/src/crud/lib/entity-endpoints.test.ts +41 -0
  22. package/src/crud/lib/entity-endpoints.ts +23 -0
  23. package/src/crud/lib/errors.ts +14 -0
  24. package/src/crud/lib/import-request.ts +99 -0
  25. package/src/crud/lib/mutation-builder.test.ts +114 -0
  26. package/src/crud/lib/mutation-builder.ts +27 -34
  27. package/src/crud/lib/permissions.test.ts +57 -0
  28. package/src/crud/lib/permissions.ts +25 -13
  29. package/src/crud/lib/query-builder.ts +13 -6
  30. package/src/crud/pages/entity-crud-page.tsx +11 -8
  31. package/src/crud/server-service.test.ts +112 -0
  32. package/src/crud/server-service.ts +52 -27
  33. package/src/crud/server.ts +11 -1
  34. package/src/guardrails/rules/design.ts +3 -2
  35. package/src/styles/base.css +16 -0
  36. package/src/types/index.ts +75 -10
  37. package/src/ui/data-display/__tests__/use-client-pagination.test.ts +59 -0
  38. package/src/ui/data-display/data-table/__tests__/data-table-row-memo.test.tsx +96 -0
  39. package/src/ui/data-display/data-table/data-table-context.tsx +5 -0
  40. package/src/ui/data-display/data-table/data-table.tsx +68 -23
  41. package/src/ui/data-display/data-table-pagination.tsx +25 -8
  42. package/src/ui/data-display/index.tsx +1 -0
  43. package/src/ui/data-display/use-client-pagination.ts +48 -0
  44. package/src/ui/errors/error-view.tsx +80 -87
  45. package/src/ui/errors/index.ts +6 -1
  46. package/src/ui/errors/not-found-view.tsx +89 -16
  47. package/src/ui/errors/status-page.tsx +132 -0
  48. package/src/ui/layout/logo.tsx +5 -0
  49. package/src/ui/pages/not-found.tsx +11 -27
  50. package/templates/starter-app/AGENTS.md +1 -1
  51. package/templates/starter-app/next.config.mjs +8 -0
  52. package/templates/starter-app/package.json +1 -0
  53. package/templates/starter-app/prisma/migrations/20260807115347_platform_pages/migration.sql +87 -0
  54. package/templates/starter-app/prisma/migrations/20260808003811_company_profile/migration.sql +25 -0
  55. package/templates/starter-app/public/images/illustrations/characters/character-01.svg +33 -0
  56. package/templates/starter-app/public/images/illustrations/characters/character-02.svg +30 -0
  57. package/templates/starter-app/public/images/illustrations/characters/character-03.svg +30 -0
  58. package/templates/starter-app/public/images/illustrations/characters/character-04.svg +27 -0
  59. package/templates/starter-app/public/images/logos/goeat_logo.png +0 -0
  60. package/templates/starter-app/scripts/rbac-sync.ts +49 -21
  61. package/templates/starter-app/src/__tests__/architecture.test.ts +3 -2
  62. package/templates/starter-app/src/app/[lang]/(main)/actions/page.tsx +4 -4
  63. package/templates/starter-app/src/app/[lang]/(main)/admin/system/cache/page.tsx +1 -2
  64. package/templates/starter-app/src/app/[lang]/(main)/admin/system/company/actions.ts +7 -5
  65. package/templates/starter-app/src/app/[lang]/(main)/admin/system/company/company-profile-form.tsx +38 -11
  66. package/templates/starter-app/src/app/[lang]/(main)/admin/system/company/page.tsx +2 -1
  67. package/templates/starter-app/src/app/[lang]/(main)/crud/[entity]/page.tsx +4 -2
  68. package/templates/starter-app/src/app/[lang]/(main)/layout.tsx +2 -1
  69. package/templates/starter-app/src/app/[lang]/(main)/page.tsx +4 -4
  70. package/templates/starter-app/src/app/[lang]/(main)/resources/page.tsx +5 -5
  71. package/templates/starter-app/src/app/[lang]/(main)/roles/[id]/edit/page.tsx +5 -2
  72. package/templates/starter-app/src/app/[lang]/(main)/roles/new/page.tsx +1 -0
  73. package/templates/starter-app/src/app/[lang]/(main)/roles/page.tsx +1 -0
  74. package/templates/starter-app/src/app/[lang]/(main)/user/profile/actions.ts +4 -2
  75. package/templates/starter-app/src/app/[lang]/(main)/user/profile/page.tsx +3 -1
  76. package/templates/starter-app/src/app/[lang]/(main)/user/profile/profile-client-page.tsx +13 -5
  77. package/templates/starter-app/src/app/[lang]/(main)/users/page.tsx +3 -2
  78. package/templates/starter-app/src/app/[lang]/layout.tsx +27 -2
  79. package/templates/starter-app/src/app/api/admin/system/audit/route.ts +4 -3
  80. package/templates/starter-app/src/app/api/admin/system/jobs/[name]/history/route.ts +1 -1
  81. package/templates/starter-app/src/app/api/admin/system/jobs/route.ts +31 -12
  82. package/templates/starter-app/src/app/api/admin/system/settings/all/route.ts +2 -2
  83. package/templates/starter-app/src/app/api/admin/system/settings/create/route.ts +10 -3
  84. package/templates/starter-app/src/app/api/admin/system/settings/delete/route.ts +10 -3
  85. package/templates/starter-app/src/app/api/admin/system/settings/route.ts +14 -5
  86. package/templates/starter-app/src/app/api/admin/system/settings/toggle-status/route.ts +16 -7
  87. package/templates/starter-app/src/app/api/admin/system/settings/update/route.ts +12 -6
  88. package/templates/starter-app/src/app/api/admin/system/settings/update-full/route.ts +12 -6
  89. package/templates/starter-app/src/app/api/better-auth/[...all]/route.ts +1 -1
  90. package/templates/starter-app/src/app/api/branding/logo/route.ts +25 -0
  91. package/templates/starter-app/src/app/api/crud/[entity]/[id]/route.ts +2 -2
  92. package/templates/starter-app/src/app/api/crud/[entity]/export/route.ts +16 -0
  93. package/templates/starter-app/src/app/api/crud/[entity]/import/route.ts +17 -0
  94. package/templates/starter-app/src/app/api/crud/[entity]/route.ts +2 -2
  95. package/templates/starter-app/src/app/api/error-logs/[id]/route.ts +4 -2
  96. package/templates/starter-app/src/app/api/error-logs/route.ts +26 -19
  97. package/templates/starter-app/src/app/api/files/[...key]/route.ts +1 -2
  98. package/templates/starter-app/src/app/api/job-titles/route.ts +1 -1
  99. package/templates/starter-app/src/app/api/notifications/read/route.ts +1 -1
  100. package/templates/starter-app/src/app/api/notifications/route.ts +1 -1
  101. package/templates/starter-app/src/app/api/notifications/unread-count/route.ts +1 -1
  102. package/templates/starter-app/src/app/api/rbac/permissions-version/route.ts +4 -1
  103. package/templates/starter-app/src/app/api/roles/[id]/route.ts +2 -1
  104. package/templates/starter-app/src/app/api/roles/route.ts +2 -1
  105. package/templates/starter-app/src/app/api/security/sessions/[id]/route.ts +5 -5
  106. package/templates/starter-app/src/app/api/security/sessions/revoke-user/route.ts +1 -1
  107. package/templates/starter-app/src/app/api/security/sessions/route.ts +2 -2
  108. package/templates/starter-app/src/app/api/system-categories/route.ts +39 -14
  109. package/templates/starter-app/src/app/api/system-category-groups/route.ts +32 -14
  110. package/templates/starter-app/src/app/api/tasks/[id]/download/route.ts +7 -3
  111. package/templates/starter-app/src/app/api/tasks/route.ts +5 -2
  112. package/templates/starter-app/src/app/api/upload/route.ts +1 -2
  113. package/templates/starter-app/src/app/api/users/[id]/route.ts +20 -12
  114. package/templates/starter-app/src/app/api/users/route.ts +11 -9
  115. package/templates/starter-app/src/app/globals.css +0 -1
  116. package/templates/starter-app/src/app/layout.tsx +42 -1
  117. package/templates/starter-app/src/app/manifest.ts +23 -19
  118. package/templates/starter-app/src/components/layout/main-layout-wrapper.tsx +5 -4
  119. package/templates/starter-app/src/configs/entities/department.config.ts +7 -4
  120. package/templates/starter-app/src/configs/entities/index.ts +1 -1
  121. package/templates/starter-app/src/configs/entities/job-titles.config.ts +2 -0
  122. package/templates/starter-app/src/configs/entities/system-alerts.config.ts +2 -0
  123. package/templates/starter-app/src/configs/entities/users.config.ts +15 -2
  124. package/templates/starter-app/src/configs/i18n.ts +6 -6
  125. package/templates/starter-app/src/configs/permissions/menu-tree.ts +1 -1
  126. package/templates/starter-app/src/configs/tenant.ts +6 -1
  127. package/templates/starter-app/src/data/dictionary.ts +2 -1
  128. package/templates/starter-app/src/data/navigations.ts +12 -2
  129. package/templates/starter-app/src/lib/action-guard.ts +3 -4
  130. package/templates/starter-app/src/lib/api-handler.ts +27 -19
  131. package/templates/starter-app/src/lib/auth.ts +3 -1
  132. package/templates/starter-app/src/lib/branch-scope.ts +1 -1
  133. package/templates/starter-app/src/lib/crud/index.ts +5 -12
  134. package/templates/starter-app/src/lib/errors/log-server-error.ts +5 -4
  135. package/templates/starter-app/src/lib/logger.ts +11 -4
  136. package/templates/starter-app/src/lib/page-guard.ts +2 -3
  137. package/templates/starter-app/src/lib/prisma.ts +13 -5
  138. package/templates/starter-app/src/lib/rbac/access.ts +2 -2
  139. package/templates/starter-app/src/lib/storage.ts +3 -1
  140. package/templates/starter-app/src/providers/index.tsx +13 -2
  141. package/templates/starter-app/src/providers/theme-provider.tsx +4 -3
  142. package/templates/starter-app/src/proxy.ts +3 -1
  143. package/templates/starter-app/src/server/services/company-service.ts +4 -2
  144. package/templates/starter-app/src/server/services/notification-service.ts +6 -6
  145. package/templates/starter-app/src/server/services/system-config-service.ts +21 -9
  146. package/templates/starter-app/src/server/services/user-service.ts +2 -2
  147. package/templates/starter-app/src/server/tasks/handlers/export-departments.ts +15 -9
  148. package/templates/starter-app/src/server/tasks/task-runner.ts +6 -7
  149. package/templates/starter-app/tsconfig.json +8 -0
  150. 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(key: string, value: unknown, userId: string) {
107
- if (!key || value === undefined) throw new ConfigError("Thiếu key hoặc value", 400)
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: input.description !== undefined ? input.description : existing.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) throw new ConfigError("Không thể xóa cấu hình được bảo vệ", 403)
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(key: string, status: string, userId: string) {
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 { registerTaskHandler, saveTaskFile } from "../task-runner"
10
+ import type { TaskContext, TaskFileResult } from "@goerp/core/tasks"
11
11
 
12
12
  import { db } from "@/lib/prisma"
13
13
 
14
- import type { TaskContext, TaskFileResult } from "@goerp/core/tasks"
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[] = [["Mã", "Tên", "Mô tả", "Thứ tự", "Trạng thái"].join(",")]
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].map(csvCell).join(","),
43
+ [d.code, d.name, d.description, d.order, d.status]
44
+ .map(csvCell)
45
+ .join(",")
42
46
  )
43
47
  }
44
- await setProgress(Math.round(((skip + page.length) / Math.max(total, 1)) * 100))
48
+ await setProgress(
49
+ Math.round(((skip + page.length) / Math.max(total, 1)) * 100)
50
+ )
45
51
  }
46
52
 
47
- // BOM  để Excel bản tiếng Việt không đọc UTF-8 thành ký tự lỗi.
48
- const buffer = Buffer.from(`${rows.join("\n")}`, "utf8")
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
- alias: { "@": path.resolve(__dirname, "./src") },
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
  })