@goplusvn/core 0.1.71 → 0.1.72

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 (124) hide show
  1. package/package.json +2 -1
  2. package/src/auth/index.ts +5 -1
  3. package/src/configs/entities/departments.config.ts +1 -0
  4. package/src/configs/entities/material-categories.config.ts +1 -0
  5. package/src/crud/components/crud-export-button.tsx +3 -2
  6. package/src/crud/components/crud-page.tsx +56 -25
  7. package/src/crud/components/crud-row-actions.tsx +16 -4
  8. package/src/crud/crud-route-handlers.test.ts +352 -0
  9. package/src/crud/crud-route-handlers.ts +260 -13
  10. package/src/crud/index.ts +3 -0
  11. package/src/crud/lib/coerce.test.ts +118 -0
  12. package/src/crud/lib/coerce.ts +95 -0
  13. package/src/crud/lib/crud-utils.ts +4 -0
  14. package/src/crud/lib/entity-endpoints.test.ts +41 -0
  15. package/src/crud/lib/entity-endpoints.ts +23 -0
  16. package/src/crud/lib/errors.ts +14 -0
  17. package/src/crud/lib/import-request.ts +99 -0
  18. package/src/crud/lib/mutation-builder.test.ts +114 -0
  19. package/src/crud/lib/mutation-builder.ts +27 -34
  20. package/src/crud/lib/permissions.test.ts +57 -0
  21. package/src/crud/lib/permissions.ts +25 -13
  22. package/src/crud/lib/query-builder.ts +13 -6
  23. package/src/crud/pages/entity-crud-page.tsx +11 -8
  24. package/src/crud/server-service.test.ts +112 -0
  25. package/src/crud/server-service.ts +52 -27
  26. package/src/crud/server.ts +11 -1
  27. package/src/guardrails/rules/design.ts +3 -2
  28. package/src/types/index.ts +75 -10
  29. package/src/ui/data-display/__tests__/use-client-pagination.test.ts +59 -0
  30. package/src/ui/data-display/data-table/__tests__/data-table-row-memo.test.tsx +96 -0
  31. package/src/ui/data-display/data-table/data-table-context.tsx +5 -0
  32. package/src/ui/data-display/data-table/data-table.tsx +68 -23
  33. package/src/ui/data-display/data-table-pagination.tsx +25 -8
  34. package/src/ui/data-display/index.tsx +1 -0
  35. package/src/ui/data-display/use-client-pagination.ts +48 -0
  36. package/templates/starter-app/AGENTS.md +1 -1
  37. package/templates/starter-app/prisma/migrations/20260807115347_platform_pages/migration.sql +87 -0
  38. package/templates/starter-app/prisma/migrations/20260808003811_company_profile/migration.sql +25 -0
  39. package/templates/starter-app/scripts/rbac-sync.ts +49 -21
  40. package/templates/starter-app/src/__tests__/architecture.test.ts +3 -2
  41. package/templates/starter-app/src/app/[lang]/(main)/actions/page.tsx +4 -4
  42. package/templates/starter-app/src/app/[lang]/(main)/admin/system/cache/page.tsx +1 -2
  43. package/templates/starter-app/src/app/[lang]/(main)/admin/system/company/actions.ts +7 -5
  44. package/templates/starter-app/src/app/[lang]/(main)/admin/system/company/company-profile-form.tsx +20 -9
  45. package/templates/starter-app/src/app/[lang]/(main)/admin/system/company/page.tsx +2 -1
  46. package/templates/starter-app/src/app/[lang]/(main)/crud/[entity]/page.tsx +4 -2
  47. package/templates/starter-app/src/app/[lang]/(main)/layout.tsx +2 -1
  48. package/templates/starter-app/src/app/[lang]/(main)/page.tsx +4 -4
  49. package/templates/starter-app/src/app/[lang]/(main)/resources/page.tsx +5 -5
  50. package/templates/starter-app/src/app/[lang]/(main)/roles/[id]/edit/page.tsx +5 -2
  51. package/templates/starter-app/src/app/[lang]/(main)/roles/new/page.tsx +1 -0
  52. package/templates/starter-app/src/app/[lang]/(main)/roles/page.tsx +1 -0
  53. package/templates/starter-app/src/app/[lang]/(main)/user/profile/actions.ts +4 -2
  54. package/templates/starter-app/src/app/[lang]/(main)/user/profile/page.tsx +3 -1
  55. package/templates/starter-app/src/app/[lang]/(main)/user/profile/profile-client-page.tsx +13 -5
  56. package/templates/starter-app/src/app/[lang]/(main)/users/page.tsx +3 -2
  57. package/templates/starter-app/src/app/[lang]/layout.tsx +2 -2
  58. package/templates/starter-app/src/app/api/admin/system/audit/route.ts +4 -3
  59. package/templates/starter-app/src/app/api/admin/system/jobs/[name]/history/route.ts +1 -1
  60. package/templates/starter-app/src/app/api/admin/system/jobs/route.ts +31 -12
  61. package/templates/starter-app/src/app/api/admin/system/settings/all/route.ts +2 -2
  62. package/templates/starter-app/src/app/api/admin/system/settings/create/route.ts +10 -3
  63. package/templates/starter-app/src/app/api/admin/system/settings/delete/route.ts +10 -3
  64. package/templates/starter-app/src/app/api/admin/system/settings/route.ts +14 -5
  65. package/templates/starter-app/src/app/api/admin/system/settings/toggle-status/route.ts +16 -7
  66. package/templates/starter-app/src/app/api/admin/system/settings/update/route.ts +12 -6
  67. package/templates/starter-app/src/app/api/admin/system/settings/update-full/route.ts +12 -6
  68. package/templates/starter-app/src/app/api/better-auth/[...all]/route.ts +1 -1
  69. package/templates/starter-app/src/app/api/crud/[entity]/[id]/route.ts +2 -2
  70. package/templates/starter-app/src/app/api/crud/[entity]/export/route.ts +16 -0
  71. package/templates/starter-app/src/app/api/crud/[entity]/import/route.ts +17 -0
  72. package/templates/starter-app/src/app/api/crud/[entity]/route.ts +2 -2
  73. package/templates/starter-app/src/app/api/error-logs/[id]/route.ts +4 -2
  74. package/templates/starter-app/src/app/api/error-logs/route.ts +26 -19
  75. package/templates/starter-app/src/app/api/files/[...key]/route.ts +1 -2
  76. package/templates/starter-app/src/app/api/job-titles/route.ts +1 -1
  77. package/templates/starter-app/src/app/api/notifications/read/route.ts +1 -1
  78. package/templates/starter-app/src/app/api/notifications/route.ts +1 -1
  79. package/templates/starter-app/src/app/api/notifications/unread-count/route.ts +1 -1
  80. package/templates/starter-app/src/app/api/rbac/permissions-version/route.ts +4 -1
  81. package/templates/starter-app/src/app/api/roles/[id]/route.ts +2 -1
  82. package/templates/starter-app/src/app/api/roles/route.ts +2 -1
  83. package/templates/starter-app/src/app/api/security/sessions/[id]/route.ts +5 -5
  84. package/templates/starter-app/src/app/api/security/sessions/revoke-user/route.ts +1 -1
  85. package/templates/starter-app/src/app/api/security/sessions/route.ts +2 -2
  86. package/templates/starter-app/src/app/api/system-categories/route.ts +39 -14
  87. package/templates/starter-app/src/app/api/system-category-groups/route.ts +32 -14
  88. package/templates/starter-app/src/app/api/tasks/[id]/download/route.ts +7 -3
  89. package/templates/starter-app/src/app/api/tasks/route.ts +5 -2
  90. package/templates/starter-app/src/app/api/upload/route.ts +1 -2
  91. package/templates/starter-app/src/app/api/users/[id]/route.ts +20 -12
  92. package/templates/starter-app/src/app/api/users/route.ts +11 -9
  93. package/templates/starter-app/src/app/globals.css +0 -1
  94. package/templates/starter-app/src/components/layout/main-layout-wrapper.tsx +5 -4
  95. package/templates/starter-app/src/configs/entities/department.config.ts +7 -4
  96. package/templates/starter-app/src/configs/entities/index.ts +1 -1
  97. package/templates/starter-app/src/configs/entities/job-titles.config.ts +2 -0
  98. package/templates/starter-app/src/configs/entities/system-alerts.config.ts +2 -0
  99. package/templates/starter-app/src/configs/entities/users.config.ts +15 -2
  100. package/templates/starter-app/src/configs/i18n.ts +6 -6
  101. package/templates/starter-app/src/configs/permissions/menu-tree.ts +1 -1
  102. package/templates/starter-app/src/data/dictionary.ts +2 -1
  103. package/templates/starter-app/src/data/navigations.ts +12 -2
  104. package/templates/starter-app/src/lib/action-guard.ts +3 -4
  105. package/templates/starter-app/src/lib/api-handler.ts +27 -19
  106. package/templates/starter-app/src/lib/auth.ts +3 -1
  107. package/templates/starter-app/src/lib/branch-scope.ts +1 -1
  108. package/templates/starter-app/src/lib/crud/index.ts +5 -12
  109. package/templates/starter-app/src/lib/errors/log-server-error.ts +5 -4
  110. package/templates/starter-app/src/lib/logger.ts +11 -4
  111. package/templates/starter-app/src/lib/page-guard.ts +2 -3
  112. package/templates/starter-app/src/lib/prisma.ts +13 -5
  113. package/templates/starter-app/src/lib/rbac/access.ts +2 -2
  114. package/templates/starter-app/src/lib/storage.ts +3 -1
  115. package/templates/starter-app/src/providers/index.tsx +2 -1
  116. package/templates/starter-app/src/providers/theme-provider.tsx +4 -3
  117. package/templates/starter-app/src/server/services/company-service.ts +4 -2
  118. package/templates/starter-app/src/server/services/notification-service.ts +6 -6
  119. package/templates/starter-app/src/server/services/system-config-service.ts +21 -9
  120. package/templates/starter-app/src/server/services/user-service.ts +2 -2
  121. package/templates/starter-app/src/server/tasks/handlers/export-departments.ts +15 -9
  122. package/templates/starter-app/src/server/tasks/task-runner.ts +6 -7
  123. package/templates/starter-app/tsconfig.json +8 -0
  124. package/templates/starter-app/vitest.config.ts +17 -1
@@ -9,6 +9,7 @@ import {
9
9
  import type { Metadata } from "next"
10
10
 
11
11
  import { dictionary } from "@/data/dictionary"
12
+
12
13
  import { requirePageAccess } from "@/lib/page-guard"
13
14
  import { db } from "@/lib/prisma"
14
15
 
@@ -29,7 +30,9 @@ export default async function EditRolePage({
29
30
  getActionsForPermissionMatrix(db),
30
31
  db.role.findUnique({
31
32
  where: { id },
32
- include: { rolePermissions: { select: { resourceCode: true, actionCode: true } } },
33
+ include: {
34
+ rolePermissions: { select: { resourceCode: true, actionCode: true } },
35
+ },
33
36
  }),
34
37
  ])
35
38
 
@@ -52,7 +55,7 @@ export default async function EditRolePage({
52
55
  // Ma trận quyền của core đọc dạng "action:resource" — đảo thứ tự là
53
56
  // không ô nào tick, mà cũng chẳng có lỗi nào hiện ra.
54
57
  permissions: role.rolePermissions.map(
55
- (rp) => `${rp.actionCode}:${rp.resourceCode}`,
58
+ (rp) => `${rp.actionCode}:${rp.resourceCode}`
56
59
  ),
57
60
  }}
58
61
  />
@@ -8,6 +8,7 @@ import {
8
8
  import type { Metadata } from "next"
9
9
 
10
10
  import { dictionary } from "@/data/dictionary"
11
+
11
12
  import { requirePageAccess } from "@/lib/page-guard"
12
13
  import { db } from "@/lib/prisma"
13
14
 
@@ -9,6 +9,7 @@ import { getRolesData } from "@goerp/core/rbac/role-service"
9
9
  import type { Metadata } from "next"
10
10
 
11
11
  import { dictionary } from "@/data/dictionary"
12
+
12
13
  import { requirePageAccess } from "@/lib/page-guard"
13
14
  import { db } from "@/lib/prisma"
14
15
 
@@ -28,7 +28,7 @@ export interface ChangePasswordState {
28
28
  */
29
29
  export async function changePassword(
30
30
  _prev: ChangePasswordState,
31
- formData: FormData,
31
+ formData: FormData
32
32
  ): Promise<ChangePasswordState> {
33
33
  // Không getSession() trần ở đây (guardrail server-action-bare-session):
34
34
  // auth.api.changePassword tự validate phiên từ headers — không phiên là ném.
@@ -38,7 +38,9 @@ export async function changePassword(
38
38
  confirmPassword: formData.get("confirmPassword"),
39
39
  })
40
40
  if (!parsed.success) {
41
- return { message: parsed.error.issues[0]?.message ?? "Dữ liệu không hợp lệ" }
41
+ return {
42
+ message: parsed.error.issues[0]?.message ?? "Dữ liệu không hợp lệ",
43
+ }
42
44
  }
43
45
 
44
46
  try {
@@ -27,7 +27,9 @@ export default async function UserProfilePage() {
27
27
  email: true,
28
28
  image: true,
29
29
  createdAt: true,
30
- userRoles: { select: { roleCode: true, role: { select: { name: true } } } },
30
+ userRoles: {
31
+ select: { roleCode: true, role: { select: { name: true } } },
32
+ },
31
33
  userBranches: {
32
34
  select: { isDefault: true, branch: { select: { name: true } } },
33
35
  },
@@ -2,8 +2,6 @@
2
2
 
3
3
  import * as React from "react"
4
4
  import { useActionState } from "react"
5
- import { toast } from "sonner"
6
-
7
5
  import {
8
6
  Avatar,
9
7
  AvatarFallback,
@@ -17,8 +15,11 @@ import {
17
15
  import { Button } from "@goerp/core/ui/primitives/button"
18
16
  import { Input } from "@goerp/core/ui/primitives/input"
19
17
  import { Label } from "@goerp/core/ui/primitives/label"
18
+ import { toast } from "sonner"
20
19
 
21
- import { changePassword, type ChangePasswordState } from "./actions"
20
+ import type { ChangePasswordState } from "./actions"
21
+
22
+ import { changePassword } from "./actions"
22
23
 
23
24
  interface ProfileUser {
24
25
  id: string
@@ -58,7 +59,10 @@ export function ProfileClientPage({ user }: { user: ProfileUser }) {
58
59
  <CardContent className="flex flex-col gap-6">
59
60
  <div className="flex items-center gap-4">
60
61
  <Avatar className="size-14">
61
- <AvatarImage src={user.image ?? undefined} alt={user.name ?? ""} />
62
+ <AvatarImage
63
+ src={user.image ?? undefined}
64
+ alt={user.name ?? ""}
65
+ />
62
66
  <AvatarFallback>
63
67
  {(user.name ?? user.email).slice(0, 2).toUpperCase()}
64
68
  </AvatarFallback>
@@ -107,7 +111,11 @@ export function ProfileClientPage({ user }: { user: ProfileUser }) {
107
111
  <CardTitle>Đổi mật khẩu</CardTitle>
108
112
  </CardHeader>
109
113
  <CardContent>
110
- <form ref={formRef} action={formAction} className="grid gap-4 md:max-w-md">
114
+ <form
115
+ ref={formRef}
116
+ action={formAction}
117
+ className="grid gap-4 md:max-w-md"
118
+ >
111
119
  <div className="space-y-2">
112
120
  <Label htmlFor="currentPassword">Mật khẩu hiện tại</Label>
113
121
  <Input
@@ -1,16 +1,17 @@
1
+ import { getUsersList } from "@/server/services/user-service"
1
2
  import { getCrudPermissions } from "@goerp/core/crud/server"
2
3
  import { getActiveRoles, getUserStats } from "@goerp/core/user"
3
4
  import { UsersClientPage } from "@goerp/core/user/client-page"
4
5
 
5
6
  import type { Metadata } from "next"
6
7
 
8
+ import { dictionary } from "@/data/dictionary"
9
+
7
10
  import { usersConfig } from "@/configs/entities/users.config"
8
11
  import { getActionMetaMap } from "@/configs/permissions"
9
12
  import { MENU_TREE } from "@/configs/permissions/menu-tree"
10
- import { dictionary } from "@/data/dictionary"
11
13
  import { requirePageAccess } from "@/lib/page-guard"
12
14
  import { db } from "@/lib/prisma"
13
- import { getUsersList } from "@/server/services/user-service"
14
15
 
15
16
  export const metadata: Metadata = { title: "Người dùng" }
16
17
  export const dynamic = "force-dynamic"
@@ -1,8 +1,8 @@
1
+ import { Providers } from "@/providers"
2
+
1
3
  import type { LocaleType } from "@goerp/core/types"
2
4
  import type { ReactNode } from "react"
3
5
 
4
- import { Providers } from "@/providers"
5
-
6
6
  /**
7
7
  * Không truyền session xuống Providers: Better Auth lấy session ở CLIENT qua
8
8
  * authClient.useSession() (bridge). Server component nào cần session thì tự gọi
@@ -18,7 +18,6 @@ export const GET = apiHandler(
18
18
  const skip = parseInt(sp.get("skip") || "0", 10)
19
19
  const take = Math.min(parseInt(sp.get("take") || "50", 10), 100)
20
20
 
21
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
21
  const where: Record<string, any> = {}
23
22
  if (action && action !== "all") where.action = action
24
23
  if (resource && resource !== "all") where.resource = resource
@@ -53,8 +52,10 @@ export const GET = apiHandler(
53
52
  meta: { total, skip, take, hasMore: skip + take < total },
54
53
  })
55
54
  } catch (error) {
56
- return serverError(error, req, { message: "Không tải được nhật ký thao tác" })
55
+ return serverError(error, req, {
56
+ message: "Không tải được nhật ký thao tác",
57
+ })
57
58
  }
58
59
  },
59
- { resource: "audit-log", action: "view" },
60
+ { resource: "audit-log", action: "view" }
60
61
  )
@@ -35,5 +35,5 @@ export const GET = apiHandler(
35
35
  return serverError(error, req, { message: "Không tải được lịch sử job" })
36
36
  }
37
37
  },
38
- { resource: "system-job", action: "view" },
38
+ { resource: "system-job", action: "view" }
39
39
  )
@@ -17,13 +17,18 @@ import { db } from "@/lib/prisma"
17
17
  export const GET = apiHandler(
18
18
  async (req) => {
19
19
  try {
20
- const memoryJobs = new Map(cronManager.getJobsInfo().map((j) => [j.name, j]))
20
+ const memoryJobs = new Map(
21
+ cronManager.getJobsInfo().map((j) => [j.name, j])
22
+ )
21
23
  const dbJobs = new Map(
22
- (await db.systemJob.findMany({ orderBy: { name: "asc" } })).map((j) => [j.name, j]),
24
+ (await db.systemJob.findMany({ orderBy: { name: "asc" } })).map((j) => [
25
+ j.name,
26
+ j,
27
+ ])
23
28
  )
24
29
 
25
- const names = [...new Set([...dbJobs.keys(), ...memoryJobs.keys()])].sort((a, b) =>
26
- a.localeCompare(b),
30
+ const names = [...new Set([...dbJobs.keys(), ...memoryJobs.keys()])].sort(
31
+ (a, b) => a.localeCompare(b)
27
32
  )
28
33
 
29
34
  const jobs = names.map((name) => {
@@ -45,10 +50,12 @@ export const GET = apiHandler(
45
50
 
46
51
  return NextResponse.json({ data: jobs })
47
52
  } catch (error) {
48
- return serverError(error, req, { message: "Không tải được danh sách job" })
53
+ return serverError(error, req, {
54
+ message: "Không tải được danh sách job",
55
+ })
49
56
  }
50
57
  },
51
- { resource: "system-job", action: "view" },
58
+ { resource: "system-job", action: "view" }
52
59
  )
53
60
 
54
61
  /** POST { name, action: "toggle" | "run" | "remove" }. */
@@ -62,7 +69,10 @@ export const POST = apiHandler(
62
69
 
63
70
  if (action === "toggle") {
64
71
  if (!(await cronManager.toggleJob(name))) {
65
- return NextResponse.json({ error: `Không tìm thấy job "${name}"` }, { status: 404 })
72
+ return NextResponse.json(
73
+ { error: `Không tìm thấy job "${name}"` },
74
+ { status: 404 }
75
+ )
66
76
  }
67
77
  const info = cronManager.getJobsInfo().find((j) => j.name === name)
68
78
  return NextResponse.json({
@@ -74,23 +84,32 @@ export const POST = apiHandler(
74
84
 
75
85
  if (action === "run") {
76
86
  if (!(await cronManager.runJobNow(name))) {
77
- return NextResponse.json({ error: `Không tìm thấy job "${name}"` }, { status: 404 })
87
+ return NextResponse.json(
88
+ { error: `Không tìm thấy job "${name}"` },
89
+ { status: 404 }
90
+ )
78
91
  }
79
- return NextResponse.json({ success: true, message: `Đã chạy tay job "${name}"` })
92
+ return NextResponse.json({
93
+ success: true,
94
+ message: `Đã chạy tay job "${name}"`,
95
+ })
80
96
  }
81
97
 
82
98
  if (action === "remove") {
83
99
  cronManager.removeJob(name)
84
- return NextResponse.json({ success: true, message: `Đã gỡ job "${name}"` })
100
+ return NextResponse.json({
101
+ success: true,
102
+ message: `Đã gỡ job "${name}"`,
103
+ })
85
104
  }
86
105
 
87
106
  return NextResponse.json(
88
107
  { error: "action phải là 'toggle', 'run' hoặc 'remove'" },
89
- { status: 400 },
108
+ { status: 400 }
90
109
  )
91
110
  } catch (error) {
92
111
  return serverError(error, req, { message: "Không thao tác được với job" })
93
112
  }
94
113
  },
95
- { resource: "system-job", action: "update" },
114
+ { resource: "system-job", action: "update" }
96
115
  )
@@ -1,8 +1,8 @@
1
1
  import { NextResponse } from "next/server"
2
+ import { listConfigs } from "@/server/services/system-config-service"
2
3
 
3
4
  import { apiHandler } from "@/lib/api-handler"
4
5
  import { serverError } from "@/lib/errors/server-error"
5
- import { listConfigs } from "@/server/services/system-config-service"
6
6
 
7
7
  /** Toàn bộ cấu hình đang bật — bảng chính của trang cài đặt hệ thống. */
8
8
  export const GET = apiHandler(
@@ -13,5 +13,5 @@ export const GET = apiHandler(
13
13
  return serverError(error, req, { message: "Không tải được cấu hình" })
14
14
  }
15
15
  },
16
- { resource: "system-setting", action: "view" },
16
+ { resource: "system-setting", action: "view" }
17
17
  )
@@ -1,8 +1,11 @@
1
1
  import { NextResponse } from "next/server"
2
+ import {
3
+ configErrorInfo,
4
+ createConfig,
5
+ } from "@/server/services/system-config-service"
2
6
 
3
7
  import { apiHandler } from "@/lib/api-handler"
4
8
  import { serverError } from "@/lib/errors/server-error"
5
- import { configErrorInfo, createConfig } from "@/server/services/system-config-service"
6
9
 
7
10
  export const POST = apiHandler(
8
11
  async (req, { session }) => {
@@ -11,9 +14,13 @@ export const POST = apiHandler(
11
14
  return NextResponse.json({ success: true, data })
12
15
  } catch (error) {
13
16
  const known = configErrorInfo(error)
14
- if (known) return NextResponse.json({ error: known.message }, { status: known.status })
17
+ if (known)
18
+ return NextResponse.json(
19
+ { error: known.message },
20
+ { status: known.status }
21
+ )
15
22
  return serverError(error, req, { message: "Không tạo được cấu hình" })
16
23
  }
17
24
  },
18
- { resource: "system-setting", action: "create" },
25
+ { resource: "system-setting", action: "create" }
19
26
  )
@@ -1,8 +1,11 @@
1
1
  import { NextResponse } from "next/server"
2
+ import {
3
+ configErrorInfo,
4
+ deleteConfig,
5
+ } from "@/server/services/system-config-service"
2
6
 
3
7
  import { apiHandler } from "@/lib/api-handler"
4
8
  import { serverError } from "@/lib/errors/server-error"
5
- import { configErrorInfo, deleteConfig } from "@/server/services/system-config-service"
6
9
 
7
10
  export const POST = apiHandler(
8
11
  async (req) => {
@@ -12,9 +15,13 @@ export const POST = apiHandler(
12
15
  return NextResponse.json({ success: true, message: "Đã xóa cấu hình" })
13
16
  } catch (error) {
14
17
  const known = configErrorInfo(error)
15
- if (known) return NextResponse.json({ error: known.message }, { status: known.status })
18
+ if (known)
19
+ return NextResponse.json(
20
+ { error: known.message },
21
+ { status: known.status }
22
+ )
16
23
  return serverError(error, req, { message: "Không xóa được cấu hình" })
17
24
  }
18
25
  },
19
- { resource: "system-setting", action: "delete" },
26
+ { resource: "system-setting", action: "delete" }
20
27
  )
@@ -23,7 +23,7 @@ export const GET = apiHandler(
23
23
  return serverError(error, req, { message: "Không tải được cấu hình" })
24
24
  }
25
25
  },
26
- { resource: "system-setting", action: "view" },
26
+ { resource: "system-setting", action: "view" }
27
27
  )
28
28
 
29
29
  export const POST = apiHandler(
@@ -33,17 +33,26 @@ export const POST = apiHandler(
33
33
 
34
34
  if (type === "audit") {
35
35
  await SettingsService.saveAuditSettings(settings)
36
- return NextResponse.json({ success: true, message: "Đã lưu cấu hình nhật ký" })
36
+ return NextResponse.json({
37
+ success: true,
38
+ message: "Đã lưu cấu hình nhật ký",
39
+ })
37
40
  }
38
41
  if (type === "notification") {
39
42
  await SettingsService.saveNotificationSettings(settings)
40
- return NextResponse.json({ success: true, message: "Đã lưu cấu hình thông báo" })
43
+ return NextResponse.json({
44
+ success: true,
45
+ message: "Đã lưu cấu hình thông báo",
46
+ })
41
47
  }
42
48
 
43
- return NextResponse.json({ error: "Loại cấu hình không hợp lệ" }, { status: 400 })
49
+ return NextResponse.json(
50
+ { error: "Loại cấu hình không hợp lệ" },
51
+ { status: 400 }
52
+ )
44
53
  } catch (error) {
45
54
  return serverError(error, req, { message: "Không lưu được cấu hình" })
46
55
  }
47
56
  },
48
- { resource: "system-setting", action: "update" },
57
+ { resource: "system-setting", action: "update" }
49
58
  )
@@ -1,12 +1,12 @@
1
1
  import { NextResponse } from "next/server"
2
-
3
- import { apiHandler } from "@/lib/api-handler"
4
- import { serverError } from "@/lib/errors/server-error"
5
2
  import {
6
3
  configErrorInfo,
7
4
  toggleConfigStatus,
8
5
  } from "@/server/services/system-config-service"
9
6
 
7
+ import { apiHandler } from "@/lib/api-handler"
8
+ import { serverError } from "@/lib/errors/server-error"
9
+
10
10
  /** Bật/tạm ngưng một cấu hình mà không xóa nó. */
11
11
  export const POST = apiHandler(
12
12
  async (req, { session }) => {
@@ -16,13 +16,22 @@ export const POST = apiHandler(
16
16
  return NextResponse.json({
17
17
  success: true,
18
18
  data,
19
- message: status === "active" ? "Đã kích hoạt cấu hình" : "Đã tạm ngưng cấu hình",
19
+ message:
20
+ status === "active"
21
+ ? "Đã kích hoạt cấu hình"
22
+ : "Đã tạm ngưng cấu hình",
20
23
  })
21
24
  } catch (error) {
22
25
  const known = configErrorInfo(error)
23
- if (known) return NextResponse.json({ error: known.message }, { status: known.status })
24
- return serverError(error, req, { message: "Không cập nhật được trạng thái" })
26
+ if (known)
27
+ return NextResponse.json(
28
+ { error: known.message },
29
+ { status: known.status }
30
+ )
31
+ return serverError(error, req, {
32
+ message: "Không cập nhật được trạng thái",
33
+ })
25
34
  }
26
35
  },
27
- { resource: "system-setting", action: "update" },
36
+ { resource: "system-setting", action: "update" }
28
37
  )
@@ -1,12 +1,12 @@
1
1
  import { NextResponse } from "next/server"
2
-
3
- import { apiHandler } from "@/lib/api-handler"
4
- import { serverError } from "@/lib/errors/server-error"
5
2
  import {
6
3
  configErrorInfo,
7
4
  updateConfigValue,
8
5
  } from "@/server/services/system-config-service"
9
6
 
7
+ import { apiHandler } from "@/lib/api-handler"
8
+ import { serverError } from "@/lib/errors/server-error"
9
+
10
10
  /** Đổi mỗi giá trị của một cấu hình theo key. */
11
11
  export const POST = apiHandler(
12
12
  async (req, { session }) => {
@@ -16,9 +16,15 @@ export const POST = apiHandler(
16
16
  return NextResponse.json({ success: true, data })
17
17
  } catch (error) {
18
18
  const known = configErrorInfo(error)
19
- if (known) return NextResponse.json({ error: known.message }, { status: known.status })
20
- return serverError(error, req, { message: "Không cập nhật được cấu hình" })
19
+ if (known)
20
+ return NextResponse.json(
21
+ { error: known.message },
22
+ { status: known.status }
23
+ )
24
+ return serverError(error, req, {
25
+ message: "Không cập nhật được cấu hình",
26
+ })
21
27
  }
22
28
  },
23
- { resource: "system-setting", action: "update" },
29
+ { resource: "system-setting", action: "update" }
24
30
  )
@@ -1,12 +1,12 @@
1
1
  import { NextResponse } from "next/server"
2
-
3
- import { apiHandler } from "@/lib/api-handler"
4
- import { serverError } from "@/lib/errors/server-error"
5
2
  import {
6
3
  configErrorInfo,
7
4
  updateConfigFull,
8
5
  } from "@/server/services/system-config-service"
9
6
 
7
+ import { apiHandler } from "@/lib/api-handler"
8
+ import { serverError } from "@/lib/errors/server-error"
9
+
10
10
  /** Sửa toàn bộ thuộc tính của một cấu hình (dialog "Sửa"). */
11
11
  export const POST = apiHandler(
12
12
  async (req, { session }) => {
@@ -15,9 +15,15 @@ export const POST = apiHandler(
15
15
  return NextResponse.json({ success: true, data })
16
16
  } catch (error) {
17
17
  const known = configErrorInfo(error)
18
- if (known) return NextResponse.json({ error: known.message }, { status: known.status })
19
- return serverError(error, req, { message: "Không cập nhật được cấu hình" })
18
+ if (known)
19
+ return NextResponse.json(
20
+ { error: known.message },
21
+ { status: known.status }
22
+ )
23
+ return serverError(error, req, {
24
+ message: "Không cập nhật được cấu hình",
25
+ })
20
26
  }
21
27
  },
22
- { resource: "system-setting", action: "update" },
28
+ { resource: "system-setting", action: "update" }
23
29
  )
@@ -9,5 +9,5 @@ import { getAuth } from "@/lib/better-auth"
9
9
  * đường đăng nhập.
10
10
  */
11
11
  export const { GET, POST } = toNextJsHandler((req: Request) =>
12
- getAuth().handler(req),
12
+ getAuth().handler(req)
13
13
  )
@@ -1,9 +1,9 @@
1
1
  import { createCrudItemHandlers } from "@goerp/core/crud/server"
2
2
 
3
- import { getSession } from "@/lib/auth"
4
3
  import { getEntityConfig } from "@/configs/entities"
5
- import { serverError } from "@/lib/errors/server-error"
4
+ import { getSession } from "@/lib/auth"
6
5
  import { crudService } from "@/lib/crud"
6
+ import { serverError } from "@/lib/errors/server-error"
7
7
 
8
8
  export const { GET, PUT, PATCH, DELETE } = createCrudItemHandlers({
9
9
  getSession,
@@ -0,0 +1,16 @@
1
+ import { createCrudExportHandler } from "@goerp/core/crud/server"
2
+
3
+ import { getEntityConfig } from "@/configs/entities"
4
+ import { getSession } from "@/lib/auth"
5
+ import { crudService } from "@/lib/crud"
6
+ import { serverError } from "@/lib/errors/server-error"
7
+
8
+ // Xuất xlsx/csv/json theo đúng search/sort/filter đang áp trên bảng. Nút
9
+ // Export của CrudPage mặc định trỏ về đây (`${apiEndpoint}/export`); gác
10
+ // quyền `<resource>:export` ở core.
11
+ export const { GET } = createCrudExportHandler({
12
+ getSession,
13
+ getEntityConfig,
14
+ service: crudService,
15
+ onError: serverError,
16
+ })
@@ -0,0 +1,17 @@
1
+ import { createCrudImportHandler } from "@goerp/core/crud/server"
2
+
3
+ import { getEntityConfig } from "@/configs/entities"
4
+ import { getSession } from "@/lib/auth"
5
+ import { crudService } from "@/lib/crud"
6
+ import { serverError } from "@/lib/errors/server-error"
7
+
8
+ // Nhập từ file (multipart của ImportDialog) hoặc JSON array. Mỗi dòng đi qua
9
+ // service.create như tạo tay — coercion/validate/audit của engine áp đủ; dòng
10
+ // lỗi gom vào ImportResult.errors, không chặn dòng sau. Gác quyền
11
+ // `<resource>:import` ở core.
12
+ export const { POST } = createCrudImportHandler({
13
+ getSession,
14
+ getEntityConfig,
15
+ service: crudService,
16
+ onError: serverError,
17
+ })
@@ -1,9 +1,9 @@
1
1
  import { createCrudCollectionHandlers } from "@goerp/core/crud/server"
2
2
 
3
- import { getSession } from "@/lib/auth"
4
3
  import { getEntityConfig } from "@/configs/entities"
5
- import { serverError } from "@/lib/errors/server-error"
4
+ import { getSession } from "@/lib/auth"
6
5
  import { crudService } from "@/lib/crud"
6
+ import { serverError } from "@/lib/errors/server-error"
7
7
 
8
8
  // GET danh sách + POST tạo. Gác quyền + query đều ở core.
9
9
  export const { GET, POST } = createCrudCollectionHandlers({
@@ -16,8 +16,10 @@ export const PATCH = apiHandler(
16
16
  })
17
17
  return NextResponse.json({ success: true, log })
18
18
  } catch (error) {
19
- return serverError(error, req, { message: "Không cập nhật được trạng thái lỗi" })
19
+ return serverError(error, req, {
20
+ message: "Không cập nhật được trạng thái lỗi",
21
+ })
20
22
  }
21
23
  },
22
- { resource: "error-log", action: "update" },
24
+ { resource: "error-log", action: "update" }
23
25
  )