@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
@@ -12,7 +12,6 @@ import { db } from "@/lib/prisma"
12
12
  * và dọn.
13
13
  */
14
14
 
15
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
15
  type Where = Record<string, any>
17
16
 
18
17
  export const GET = apiHandler(
@@ -21,7 +20,7 @@ export const GET = apiHandler(
21
20
  const sp = new URL(req.url).searchParams
22
21
  const search = sp.get("search") || undefined
23
22
  const severity = sp.get("severity") || undefined
24
- const module = sp.get("module") || undefined
23
+ const moduleFilter = sp.get("module") || undefined
25
24
  const resolved = sp.get("resolved")
26
25
  const startDate = sp.get("startDate")
27
26
  const endDate = sp.get("endDate")
@@ -38,7 +37,7 @@ export const GET = apiHandler(
38
37
  ]
39
38
  }
40
39
  if (severity && severity !== "all") where.severity = severity
41
- if (module && module !== "all") where.module = module
40
+ if (moduleFilter && moduleFilter !== "all") where.module = moduleFilter
42
41
  if (resolved === "true") where.resolved = true
43
42
  else if (resolved === "false") where.resolved = false
44
43
  if (startDate || endDate) {
@@ -50,23 +49,31 @@ export const GET = apiHandler(
50
49
  const startOfToday = new Date()
51
50
  startOfToday.setHours(0, 0, 0, 0)
52
51
 
53
- const [logs, total, todayCount, unresolvedCount, moduleStats] = await Promise.all([
54
- db.errorLog.findMany({ where, orderBy: { lastSeenAt: "desc" }, skip, take }),
55
- db.errorLog.count({ where }),
56
- db.errorLog.count({ where: { createdAt: { gte: startOfToday } } }),
57
- db.errorLog.count({ where: { resolved: false } }),
58
- db.errorLog.groupBy({
59
- by: ["module"],
60
- _count: { module: true },
61
- where: { resolved: false },
62
- orderBy: { _count: { module: "desc" } },
63
- take: 8,
64
- }),
65
- ])
52
+ const [logs, total, todayCount, unresolvedCount, moduleStats] =
53
+ await Promise.all([
54
+ db.errorLog.findMany({
55
+ where,
56
+ orderBy: { lastSeenAt: "desc" },
57
+ skip,
58
+ take,
59
+ }),
60
+ db.errorLog.count({ where }),
61
+ db.errorLog.count({ where: { createdAt: { gte: startOfToday } } }),
62
+ db.errorLog.count({ where: { resolved: false } }),
63
+ db.errorLog.groupBy({
64
+ by: ["module"],
65
+ _count: { module: true },
66
+ where: { resolved: false },
67
+ orderBy: { _count: { module: "desc" } },
68
+ take: 8,
69
+ }),
70
+ ])
66
71
 
67
72
  // ErrorLog không có quan hệ tới User (lỗi có thể xảy ra khi chưa đăng
68
73
  // nhập) — tra tên riêng thay vì include.
69
- const userIds = [...new Set(logs.map((l) => l.userId).filter(Boolean))] as string[]
74
+ const userIds = [
75
+ ...new Set(logs.map((l) => l.userId).filter(Boolean)),
76
+ ] as string[]
70
77
  const nameById = new Map<string, string>()
71
78
  if (userIds.length) {
72
79
  const users = await db.user.findMany({
@@ -98,7 +105,7 @@ export const GET = apiHandler(
98
105
  return serverError(error, req, { message: "Không tải được nhật ký lỗi" })
99
106
  }
100
107
  },
101
- { resource: "error-log", action: "view" },
108
+ { resource: "error-log", action: "view" }
102
109
  )
103
110
 
104
111
  /** Dọn log: ?mode=resolved (đã xử lý) hoặc ?mode=older_than&days=30. */
@@ -120,5 +127,5 @@ export const DELETE = apiHandler(
120
127
  return serverError(error, req, { message: "Không xóa được nhật ký lỗi" })
121
128
  }
122
129
  },
123
- { resource: "error-log", action: "delete" },
130
+ { resource: "error-log", action: "delete" }
124
131
  )
@@ -1,6 +1,5 @@
1
- import { createFileProxyHandler } from "@/lib/storage"
2
-
3
1
  import { apiHandler } from "@/lib/api-handler"
2
+ import { createFileProxyHandler } from "@/lib/storage"
4
3
 
5
4
  /**
6
5
  * GET /api/files/[...key] — phục vụ tập tin từ kho qua app server.
@@ -40,7 +40,7 @@ export const GET = apiHandler(
40
40
  ])
41
41
 
42
42
  return NextResponse.json({ total, page, pageSize, items })
43
- },
43
+ }
44
44
  // Combo tra cứu — registry khai lookupPolicy "authenticated" cho job-title:
45
45
  // ai đăng nhập cũng đổ được picker, không đòi quyền view của trang quản lý.
46
46
  )
@@ -1,8 +1,8 @@
1
1
  import { NextResponse } from "next/server"
2
+ import { markAllRead, markRead } from "@/server/services/notification-service"
2
3
 
3
4
  import { apiHandler } from "@/lib/api-handler"
4
5
  import { serverError } from "@/lib/errors/server-error"
5
- import { markAllRead, markRead } from "@/server/services/notification-service"
6
6
 
7
7
  /**
8
8
  * POST /api/notifications/read
@@ -1,8 +1,8 @@
1
1
  import { NextResponse } from "next/server"
2
+ import { listNotifications } from "@/server/services/notification-service"
2
3
 
3
4
  import { apiHandler } from "@/lib/api-handler"
4
5
  import { serverError } from "@/lib/errors/server-error"
5
- import { listNotifications } from "@/server/services/notification-service"
6
6
 
7
7
  /**
8
8
  * GET /api/notifications?cursor=&take=&filter=unread|all
@@ -1,8 +1,8 @@
1
1
  import { NextResponse } from "next/server"
2
+ import { getUnreadCount } from "@/server/services/notification-service"
2
3
 
3
4
  import { apiHandler } from "@/lib/api-handler"
4
5
  import { serverError } from "@/lib/errors/server-error"
5
- import { getUnreadCount } from "@/server/services/notification-service"
6
6
 
7
7
  /** GET /api/notifications/unread-count → { count }. Endpoint nhẹ cho chuông poll. */
8
8
  export const GET = apiHandler(async (req, { session }) => {
@@ -8,4 +8,7 @@ import { db } from "@/lib/prisma"
8
8
  * của core). Số version đổi ⇒ trình duyệt tự nạp lại phiên, nên admin sửa
9
9
  * quyền là người dùng thấy ngay, không phải đăng xuất/đăng nhập lại.
10
10
  */
11
- export const { GET } = createPermissionsVersionHandler({ prisma: db, getSession })
11
+ export const { GET } = createPermissionsVersionHandler({
12
+ prisma: db,
13
+ getSession,
14
+ })
@@ -10,5 +10,6 @@ export const { GET, PUT, DELETE } = createRoleItemHandlers({
10
10
  prisma: db,
11
11
  getSession,
12
12
  getCrudPermissions,
13
- onError: (error, req) => serverError(error, req, { message: "Lỗi xử lý vai trò" }),
13
+ onError: (error, req) =>
14
+ serverError(error, req, { message: "Lỗi xử lý vai trò" }),
14
15
  })
@@ -14,5 +14,6 @@ export const { GET, POST } = createRolesCollectionHandlers({
14
14
  prisma: db,
15
15
  getSession,
16
16
  getCrudPermissions,
17
- onError: (error, req) => serverError(error, req, { message: "Lỗi xử lý vai trò" }),
17
+ onError: (error, req) =>
18
+ serverError(error, req, { message: "Lỗi xử lý vai trò" }),
18
19
  })
@@ -22,16 +22,16 @@ export const DELETE = apiHandler(async (req, { session, params }) => {
22
22
  if (!row) {
23
23
  return NextResponse.json(
24
24
  { error: "Phiên không tồn tại" },
25
- { status: 404 },
25
+ { status: 404 }
26
26
  )
27
27
  }
28
28
 
29
29
  const isMine = row.userId === session.user.id
30
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
- if (!isMine && !checkPermission(session as Parameters<typeof checkPermission>[0], "user", "update")) {
30
+
31
+ if (!isMine && !checkPermission(session, "user", "update")) {
32
32
  return NextResponse.json(
33
33
  { error: "Bạn không có quyền thu hồi phiên của người dùng khác" },
34
- { status: 403 },
34
+ { status: 403 }
35
35
  )
36
36
  }
37
37
 
@@ -40,7 +40,7 @@ export const DELETE = apiHandler(async (req, { session, params }) => {
40
40
  if (current?.session?.token && row.token === current.session.token) {
41
41
  return NextResponse.json(
42
42
  { error: "Không thể thu hồi phiên hiện tại — hãy dùng Đăng xuất" },
43
- { status: 400 },
43
+ { status: 400 }
44
44
  )
45
45
  }
46
46
 
@@ -40,5 +40,5 @@ export const POST = apiHandler(
40
40
  return serverError(error, req, { message: "Không thu hồi được phiên" })
41
41
  }
42
42
  },
43
- { resource: "user", action: "update" },
43
+ { resource: "user", action: "update" }
44
44
  )
@@ -19,7 +19,7 @@ export const GET = apiHandler(
19
19
  const page = Math.max(Number(sp.get("page") || 1), 1)
20
20
  const pageSize = Math.min(
21
21
  Math.max(Number(sp.get("pageSize") || 20), 1),
22
- 100,
22
+ 100
23
23
  )
24
24
 
25
25
  // Token của phiên hiện tại (đánh dấu "thiết bị này" — so sánh server-side,
@@ -97,5 +97,5 @@ export const GET = apiHandler(
97
97
  })
98
98
  }
99
99
  },
100
- { resource: "user", action: "view" },
100
+ { resource: "user", action: "view" }
101
101
  )
@@ -30,7 +30,12 @@ export const GET = apiHandler(
30
30
  OR: [
31
31
  { code: { contains: search, mode: "insensitive" as const } },
32
32
  { name: { contains: search, mode: "insensitive" as const } },
33
- { description: { contains: search, mode: "insensitive" as const } },
33
+ {
34
+ description: {
35
+ contains: search,
36
+ mode: "insensitive" as const,
37
+ },
38
+ },
34
39
  ],
35
40
  }
36
41
  : {},
@@ -51,7 +56,7 @@ export const GET = apiHandler(
51
56
 
52
57
  return NextResponse.json({ total, page, pageSize, items })
53
58
  },
54
- { resource: "system-category", action: "view" },
59
+ { resource: "system-category", action: "view" }
55
60
  )
56
61
 
57
62
  export const POST = apiHandler(
@@ -61,14 +66,22 @@ export const POST = apiHandler(
61
66
 
62
67
  if (!code || !name || !groupCode) {
63
68
  return NextResponse.json(
64
- { error: "Mã, tên và nhóm danh mục là bắt buộc", field: !code ? "code" : !name ? "name" : "groupCode" },
65
- { status: 400 },
69
+ {
70
+ error: "Mã, tên và nhóm danh mục là bắt buộc",
71
+ field: !code ? "code" : !name ? "name" : "groupCode",
72
+ },
73
+ { status: 400 }
66
74
  )
67
75
  }
68
76
 
69
- const existing = await db.systemCategory.findUnique({ where: { code: String(code) } })
77
+ const existing = await db.systemCategory.findUnique({
78
+ where: { code: String(code) },
79
+ })
70
80
  if (existing) {
71
- return NextResponse.json({ error: "Mã danh mục đã tồn tại", field: "code" }, { status: 400 })
81
+ return NextResponse.json(
82
+ { error: "Mã danh mục đã tồn tại", field: "code" },
83
+ { status: 400 }
84
+ )
72
85
  }
73
86
 
74
87
  const userId = session.user.id
@@ -89,7 +102,7 @@ export const POST = apiHandler(
89
102
 
90
103
  return NextResponse.json(created, { status: 201 })
91
104
  },
92
- { resource: "system-category", action: "create" },
105
+ { resource: "system-category", action: "create" }
93
106
  )
94
107
 
95
108
  export const PUT = apiHandler(
@@ -97,14 +110,20 @@ export const PUT = apiHandler(
97
110
  const body = await req.json()
98
111
  const id = idFromRequest(req, body)
99
112
  if (!id) {
100
- return NextResponse.json({ error: "ID là bắt buộc", field: "id" }, { status: 400 })
113
+ return NextResponse.json(
114
+ { error: "ID là bắt buộc", field: "id" },
115
+ { status: 400 }
116
+ )
101
117
  }
102
118
 
103
119
  const { code, name, groupCode } = body as Record<string, unknown>
104
120
  if (!code || !name || !groupCode) {
105
121
  return NextResponse.json(
106
- { error: "Mã, tên và nhóm danh mục là bắt buộc", field: !code ? "code" : !name ? "name" : "groupCode" },
107
- { status: 400 },
122
+ {
123
+ error: "Mã, tên và nhóm danh mục là bắt buộc",
124
+ field: !code ? "code" : !name ? "name" : "groupCode",
125
+ },
126
+ { status: 400 }
108
127
  )
109
128
  }
110
129
 
@@ -113,7 +132,10 @@ export const PUT = apiHandler(
113
132
  where: { code: String(code), id: { not: id } },
114
133
  })
115
134
  if (existing) {
116
- return NextResponse.json({ error: "Mã danh mục đã tồn tại", field: "code" }, { status: 400 })
135
+ return NextResponse.json(
136
+ { error: "Mã danh mục đã tồn tại", field: "code" },
137
+ { status: 400 }
138
+ )
117
139
  }
118
140
 
119
141
  const updated = await db.systemCategory.update({
@@ -132,7 +154,7 @@ export const PUT = apiHandler(
132
154
 
133
155
  return NextResponse.json(updated)
134
156
  },
135
- { resource: "system-category", action: "update" },
157
+ { resource: "system-category", action: "update" }
136
158
  )
137
159
 
138
160
  export const DELETE = apiHandler(
@@ -144,11 +166,14 @@ export const DELETE = apiHandler(
144
166
 
145
167
  const existing = await db.systemCategory.findUnique({ where: { id } })
146
168
  if (!existing) {
147
- return NextResponse.json({ error: "Không tìm thấy danh mục" }, { status: 404 })
169
+ return NextResponse.json(
170
+ { error: "Không tìm thấy danh mục" },
171
+ { status: 404 }
172
+ )
148
173
  }
149
174
 
150
175
  await db.systemCategory.delete({ where: { id } })
151
176
  return NextResponse.json({ message: "Đã xóa danh mục" })
152
177
  },
153
- { resource: "system-category", action: "delete" },
178
+ { resource: "system-category", action: "delete" }
154
179
  )
@@ -24,7 +24,7 @@ export const GET = apiHandler(
24
24
  })
25
25
  return NextResponse.json(result)
26
26
  },
27
- { resource: "system-category", action: "view" },
27
+ { resource: "system-category", action: "view" }
28
28
  )
29
29
 
30
30
  export const POST = apiHandler(
@@ -35,13 +35,18 @@ export const POST = apiHandler(
35
35
  if (!code || !name) {
36
36
  return NextResponse.json(
37
37
  { error: "Mã và tên nhóm là bắt buộc", field: !code ? "code" : "name" },
38
- { status: 400 },
38
+ { status: 400 }
39
39
  )
40
40
  }
41
41
 
42
- const existing = await db.systemCategoryGroup.findUnique({ where: { code: String(code) } })
42
+ const existing = await db.systemCategoryGroup.findUnique({
43
+ where: { code: String(code) },
44
+ })
43
45
  if (existing) {
44
- return NextResponse.json({ error: "Mã nhóm đã tồn tại", field: "code" }, { status: 400 })
46
+ return NextResponse.json(
47
+ { error: "Mã nhóm đã tồn tại", field: "code" },
48
+ { status: 400 }
49
+ )
45
50
  }
46
51
 
47
52
  const userId = session.user.id
@@ -60,7 +65,7 @@ export const POST = apiHandler(
60
65
 
61
66
  return NextResponse.json(created, { status: 201 })
62
67
  },
63
- { resource: "system-category", action: "create" },
68
+ { resource: "system-category", action: "create" }
64
69
  )
65
70
 
66
71
  export const PUT = apiHandler(
@@ -68,14 +73,17 @@ export const PUT = apiHandler(
68
73
  const body = await req.json()
69
74
  const id = idFromRequest(req, body)
70
75
  if (!id) {
71
- return NextResponse.json({ error: "ID là bắt buộc", field: "id" }, { status: 400 })
76
+ return NextResponse.json(
77
+ { error: "ID là bắt buộc", field: "id" },
78
+ { status: 400 }
79
+ )
72
80
  }
73
81
 
74
82
  const { code, name } = body as Record<string, unknown>
75
83
  if (!code || !name) {
76
84
  return NextResponse.json(
77
85
  { error: "Mã và tên nhóm là bắt buộc", field: !code ? "code" : "name" },
78
- { status: 400 },
86
+ { status: 400 }
79
87
  )
80
88
  }
81
89
 
@@ -83,7 +91,10 @@ export const PUT = apiHandler(
83
91
  where: { code: String(code), id: { not: id } },
84
92
  })
85
93
  if (existing) {
86
- return NextResponse.json({ error: "Mã nhóm đã tồn tại", field: "code" }, { status: 400 })
94
+ return NextResponse.json(
95
+ { error: "Mã nhóm đã tồn tại", field: "code" },
96
+ { status: 400 }
97
+ )
87
98
  }
88
99
 
89
100
  const updated = await db.systemCategoryGroup.update({
@@ -100,7 +111,7 @@ export const PUT = apiHandler(
100
111
 
101
112
  return NextResponse.json(updated)
102
113
  },
103
- { resource: "system-category", action: "update" },
114
+ { resource: "system-category", action: "update" }
104
115
  )
105
116
 
106
117
  export const DELETE = apiHandler(
@@ -112,20 +123,27 @@ export const DELETE = apiHandler(
112
123
 
113
124
  const group = await db.systemCategoryGroup.findUnique({ where: { id } })
114
125
  if (!group) {
115
- return NextResponse.json({ error: "Không tìm thấy nhóm danh mục" }, { status: 404 })
126
+ return NextResponse.json(
127
+ { error: "Không tìm thấy nhóm danh mục" },
128
+ { status: 404 }
129
+ )
116
130
  }
117
131
 
118
132
  // Chặn xoá nhóm còn mục con — tránh mồ côi groupCode.
119
- const childCount = await db.systemCategory.count({ where: { groupCode: group.code } })
133
+ const childCount = await db.systemCategory.count({
134
+ where: { groupCode: group.code },
135
+ })
120
136
  if (childCount > 0) {
121
137
  return NextResponse.json(
122
- { error: `Nhóm còn ${childCount} danh mục con, hãy xóa hoặc chuyển chúng trước` },
123
- { status: 400 },
138
+ {
139
+ error: `Nhóm còn ${childCount} danh mục con, hãy xóa hoặc chuyển chúng trước`,
140
+ },
141
+ { status: 400 }
124
142
  )
125
143
  }
126
144
 
127
145
  await db.systemCategoryGroup.delete({ where: { id } })
128
146
  return NextResponse.json({ message: "Đã xóa nhóm danh mục" })
129
147
  },
130
- { resource: "system-category", action: "delete" },
148
+ { resource: "system-category", action: "delete" }
131
149
  )
@@ -20,7 +20,7 @@ export const GET = apiHandler(async (req, { session, params }) => {
20
20
  if (!task || task.status !== "success") {
21
21
  return NextResponse.json(
22
22
  { error: "Không tìm thấy tác vụ hoặc tác vụ chưa hoàn tất" },
23
- { status: 404 },
23
+ { status: 404 }
24
24
  )
25
25
  }
26
26
 
@@ -33,9 +33,13 @@ export const GET = apiHandler(async (req, { session, params }) => {
33
33
  }
34
34
  const wantError = new URL(req.url).searchParams.get("file") === "error"
35
35
  const fileKey = wantError ? result.errorFileKey : result.fileKey
36
- const fileName = (wantError ? result.errorFileName : result.fileName) || "download"
36
+ const fileName =
37
+ (wantError ? result.errorFileName : result.fileName) || "download"
37
38
  if (!fileKey) {
38
- return NextResponse.json({ error: "Tác vụ không có file kết quả" }, { status: 404 })
39
+ return NextResponse.json(
40
+ { error: "Tác vụ không có file kết quả" },
41
+ { status: 404 }
42
+ )
39
43
  }
40
44
 
41
45
  const { readTaskFile } = await import("@/server/tasks")
@@ -12,7 +12,8 @@ import { db } from "@/lib/prisma"
12
12
  export const GET = apiHandler(async (req, { session }) => {
13
13
  try {
14
14
  const takeRaw = Number(new URL(req.url).searchParams.get("take"))
15
- const take = Number.isFinite(takeRaw) && takeRaw > 0 ? Math.min(takeRaw, 100) : 20
15
+ const take =
16
+ Number.isFinite(takeRaw) && takeRaw > 0 ? Math.min(takeRaw, 100) : 20
16
17
 
17
18
  const tasks = await db.backgroundTask.findMany({
18
19
  where: { createdBy: session.user.id },
@@ -32,6 +33,8 @@ export const GET = apiHandler(async (req, { session }) => {
32
33
  })
33
34
  return NextResponse.json({ tasks })
34
35
  } catch (error) {
35
- return serverError(error, req, { message: "Không tải được danh sách tác vụ" })
36
+ return serverError(error, req, {
37
+ message: "Không tải được danh sách tác vụ",
38
+ })
36
39
  }
37
40
  })
@@ -1,6 +1,5 @@
1
- import { createUploadHandler } from "@/lib/storage"
2
-
3
1
  import { apiHandler } from "@/lib/api-handler"
2
+ import { createUploadHandler } from "@/lib/storage"
4
3
 
5
4
  /**
6
5
  * POST /api/upload — nhận tập tin đính kèm, trả `{ url, key }`.
@@ -1,5 +1,8 @@
1
1
  import { NextResponse } from "next/server"
2
- import { bumpPermissionsVersion, invalidateUserPermissions } from "@goerp/core/rbac"
2
+ import {
3
+ bumpPermissionsVersion,
4
+ invalidateUserPermissions,
5
+ } from "@goerp/core/rbac"
3
6
  import bcrypt from "bcryptjs"
4
7
 
5
8
  import { apiHandler } from "@/lib/api-handler"
@@ -39,7 +42,7 @@ export const GET = apiHandler(
39
42
  if (!user) {
40
43
  return NextResponse.json(
41
44
  { error: "Không tìm thấy người dùng" },
42
- { status: 404 },
45
+ { status: 404 }
43
46
  )
44
47
  }
45
48
  const { userRoles, userBranches, ...base } = user
@@ -53,10 +56,12 @@ export const GET = apiHandler(
53
56
  null,
54
57
  })
55
58
  } catch (error) {
56
- return serverError(error, req, { message: "Lỗi tải thông tin người dùng" })
59
+ return serverError(error, req, {
60
+ message: "Lỗi tải thông tin người dùng",
61
+ })
57
62
  }
58
63
  },
59
- { resource: "user", action: "view" },
64
+ { resource: "user", action: "view" }
60
65
  )
61
66
 
62
67
  export const PUT = apiHandler(
@@ -81,19 +86,19 @@ export const PUT = apiHandler(
81
86
  if (!name) {
82
87
  return NextResponse.json(
83
88
  { error: "Tên là bắt buộc", field: "name" },
84
- { status: 400 },
89
+ { status: 400 }
85
90
  )
86
91
  }
87
92
  if (!email) {
88
93
  return NextResponse.json(
89
94
  { error: "Email là bắt buộc", field: "email" },
90
- { status: 400 },
95
+ { status: 400 }
91
96
  )
92
97
  }
93
98
  if (password && password.length < 6) {
94
99
  return NextResponse.json(
95
100
  { error: "Mật khẩu phải có ít nhất 6 ký tự", field: "password" },
96
- { status: 400 },
101
+ { status: 400 }
97
102
  )
98
103
  }
99
104
 
@@ -104,7 +109,7 @@ export const PUT = apiHandler(
104
109
  if (duplicated) {
105
110
  return NextResponse.json(
106
111
  { error: "Email đã tồn tại", field: "email" },
107
- { status: 400 },
112
+ { status: 400 }
108
113
  )
109
114
  }
110
115
 
@@ -191,18 +196,21 @@ export const PUT = apiHandler(
191
196
  return serverError(error, req, { message: "Lỗi cập nhật người dùng" })
192
197
  }
193
198
  },
194
- { resource: "user", action: "update" },
199
+ { resource: "user", action: "update" }
195
200
  )
196
201
 
197
202
  export const DELETE = apiHandler(
198
203
  async (req, { params }) => {
199
204
  try {
200
205
  const { id } = await params
201
- const user = await db.user.findUnique({ where: { id }, select: { id: true } })
206
+ const user = await db.user.findUnique({
207
+ where: { id },
208
+ select: { id: true },
209
+ })
202
210
  if (!user) {
203
211
  return NextResponse.json(
204
212
  { error: "Không tìm thấy người dùng" },
205
- { status: 404 },
213
+ { status: 404 }
206
214
  )
207
215
  }
208
216
 
@@ -218,5 +226,5 @@ export const DELETE = apiHandler(
218
226
  return serverError(error, req, { message: "Lỗi vô hiệu hóa người dùng" })
219
227
  }
220
228
  },
221
- { resource: "user", action: "delete" },
229
+ { resource: "user", action: "delete" }
222
230
  )