diff --git a/plm-api/src/controllers/app/common/oemmng.controller.js b/plm-api/src/controllers/app/common/oemmng.controller.js new file mode 100644 index 0000000..a04ccc2 --- /dev/null +++ b/plm-api/src/controllers/app/common/oemmng.controller.js @@ -0,0 +1,114 @@ +// src/controllers/admin/users/users.controller.js +const express = require("express"); +const router = express.Router(); +const oemMngService = require("../../../services/oemmng.service"); +const authMiddleware = require("../../../middleware/auth.middleware"); +const roleCheck = require("../../../middleware/roleCheck.middleware"); +const { body, param } = require("express-validator"); +const validate = require("../../../middleware/validator.middleware"); + +router.use(authMiddleware); +router.use(roleCheck(["super_admin", "company_admin"])); + +// 고객사 목록 조회 +router.get("/oemmngList", async (req, res, next) => { + try { + const page = parseInt(req.query.page) || 1; + const limit = parseInt(req.query.limit) || 10; + + const result = await oemMngService.findAll(req.user, page, limit); + res.json(result); + } catch (error) { + next(error); + } +}); + +// Create OemMng +router.post( + "/oemmngCreate", + [ + body("oem_code").notEmpty().withMessage("OEM 코드가 필요합니다"), + body("oem_name").notEmpty().withMessage("OEM 이름이 필요합니다"), + body("companyId").isUUID().withMessage("유효한 회사 ID가 필요합니다"), + validate, + ], + async (req, res, next) => { + try { + const oemMng = await oemMngService.createOemMng(req.body, req.user); + res.status(201).json(oemMng); + } catch (error) { + next(error); + } + } +); + +// Update OemMng +router.put( + "/oemmngUpdate/:id", + [ + param("id").isUUID().withMessage("유효한 ID가 필요합니다"), + body("oem_code").optional().notEmpty().withMessage("OEM 코드가 필요합니다"), + body("oem_name").optional().notEmpty().withMessage("OEM 이름이 필요합니다"), + body("companyId").optional().isUUID().withMessage("유효한 회사 ID가 필요합니다"), + validate, + ], + async (req, res, next) => { + try { + const oemMng = await oemMngService.findById(req.params.id); + + if (!oemMng) { + return res.status(404).json({ message: "고객사를 찾을 수 없습니다" }); + } + + // company_admin은 자신의 회사 고객사만 수정 가능 + if ( + req.user.role === "company_admin" && + oemMng.companyId !== req.user.companyId + ) { + return res.status(403).json({ + message: "다른 회사의 고객사를 수정할 수 없습니다", + }); + } + + const updatedOemMng = await oemMngService.updateOemMng(req.params.id, req.body, req.user); + res.json(updatedOemMng); + } catch (error) { + next(error); + } + } +); + +// Delete OemMng +router.delete( + "/oemmngDelete/:id", + [ + param("id").isUUID().withMessage("유효한 ID가 필요합니다"), + validate, + ], + async (req, res, next) => { + try { + const oemMng = await oemMngService.findById(req.params.id); + + if (!oemMng) { + return res.status(404).json({ message: "고객사를 찾을 수 없습니다" }); + } + + // company_admin은 자신의 회사 고객사만 삭제 가능 + if ( + req.user.role === "company_admin" && + oemMng.companyId !== req.user.companyId + ) { + return res.status(403).json({ + message: "다른 회사의 고객사를 삭제할 수 없습니다", + }); + } + + await oemMngService.deleteOemMng(req.params.id, req.user); + res.status(204).end(); + } catch (error) { + next(error); + } + } +); + +module.exports = router; diff --git a/plm-api/src/models/Company.js b/plm-api/src/models/Company.js index 0918f6e..69623de 100644 --- a/plm-api/src/models/Company.js +++ b/plm-api/src/models/Company.js @@ -67,6 +67,7 @@ class Company extends Model { this.hasMany(models.Equipment, { foreignKey: "companyId" }); this.hasMany(models.MaintenanceLog, { foreignKey: "companyId" }); this.hasMany(models.ApiKey, { foreignKey: "companyId" }); + this.hasMany(models.OemMng, { foreignKey: "companyId" }); } } diff --git a/plm-api/src/models/OemMng.js b/plm-api/src/models/OemMng.js index 572c18a..4689b38 100644 --- a/plm-api/src/models/OemMng.js +++ b/plm-api/src/models/OemMng.js @@ -27,14 +27,19 @@ class OemMng extends Model { type: DataTypes.DATE, allowNull: true, }, - status: { - type: DataTypes.STRING(32), - allowNull: true, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true, }, oem_no: { type: DataTypes.STRING(32), allowNull: true, }, + // 새로 추가할 필드들 + companyId: { + type: DataTypes.UUID, + comment: "회사 ID", + }, }, { sequelize, @@ -50,6 +55,12 @@ class OemMng extends Model { ); return this; } + + static associate(models) { + // OemMng이 Company에 속함 + this.belongsTo(models.Company, { foreignKey: "companyId" }); + } + } module.exports = OemMng; \ No newline at end of file diff --git a/plm-api/src/routes/app.js b/plm-api/src/routes/app.js index 4e572c6..afb3f5b 100644 --- a/plm-api/src/routes/app.js +++ b/plm-api/src/routes/app.js @@ -17,6 +17,7 @@ const healthController = require("../controllers/app/health/health.controller"); const companiesController = require("../controllers/admin/companies/companies.controller"); const deviceController = require("../controllers/app/device/device.controller"); const commonController = require("../controllers/app/common/common.controller"); +const oemMngController = require("../controllers/app/common/oemmng.controller"); router.use("/health", healthController); router.use("/auth", authController); @@ -33,5 +34,6 @@ router.use("/department", departmentController); router.use("/companies", companiesController); router.use("/devices", deviceController); router.use("/common", commonController); +router.use("/oemmng", oemMngController); module.exports = router; diff --git a/plm-api/src/services/oemmng.service.js b/plm-api/src/services/oemmng.service.js index e6238d5..b319ab1 100644 --- a/plm-api/src/services/oemmng.service.js +++ b/plm-api/src/services/oemmng.service.js @@ -1,53 +1,36 @@ -// src/services/oemmng.service.js const { - User, + OemMng, Company, - Branch, - Department, - UserRole, Role, } = require("../models"); -const { Op } = require("sequelize"); +//const { Op } = require("sequelize"); const alertService = require("./alert.service"); class OemMngService { async findAll(currentUser, page = 1, limit = 10) { try { // Initialize the where clause - let where = { - role: { [Op.ne]: "super_admin" }, // Exclude super_admin users - }; + let where = {}; // company_admin은 자신의 회사 유저만 조회 가능 if (currentUser.role === "company_admin") { where.companyId = currentUser.companyId; } + // isActive 필터 추가 + //where.isActive = { [Op.eq]: true }; const offset = (page - 1) * limit; // 전체 개수 조회 - const count = await User.count({ where }); + const count = await OemMng.count({ where }); - const users = await User.findAll({ + const oemMngs = await OemMng.findAll({ where, include: [ { model: Company, attributes: ["id", "name"], }, - { - model: Branch, - attributes: ["id", "name"], - }, - { - model: Department, - attributes: ["id", "name"], - }, - { - model: Role, - through: { attributes: [] }, - attributes: ["id", "name", "description"], - }, ], order: [["createdAt", "DESC"]], offset, @@ -56,17 +39,17 @@ class OemMngService { }); // 인덱스 추가 - const usersWithIndex = users.map((user, index) => { - const userJson = user.toJSON(); - userJson.index = offset + index + 1; - return userJson; + const oemMngsWithIndex = oemMngs.map((oem, index) => { + const oemJson = oem.toJSON(); + oemJson.index = offset + index + 1; + return oemJson; }); return { total: count, totalPages: Math.ceil(count / limit), currentPage: page, - users: usersWithIndex, + oemMngs: oemMngsWithIndex, }; } catch (error) { console.error("Error in findAll:", error); @@ -75,34 +58,26 @@ class OemMngService { } async findById(id) { - return await User.findByPk(id, { + return await OemMng.findByPk(id, { include: [ { model: Company, attributes: ["id", "name"], }, - { - model: Branch, - attributes: ["id", "name"], - }, - { - model: Department, - attributes: ["id", "name"], - }, ], }); } - async createUser(userData, currentUser) { - const { roleId, ...userFields } = userData; + async createOemMng(oemMngData, currentUser) { + const { roleId, ...oemMngFields } = oemMngData; - const user = await User.create(userFields); + const oemMng = await OemMng.create(oemMngFields); if (roleId) { const role = await Role.findOne({ where: { id: roleId, - companyId: user.companyId, + companyId: oemMng.companyId, }, }); @@ -110,26 +85,23 @@ class OemMngService { throw new Error("Role not found or does not belong to the company"); } - await UserRole.create({ - userId: user.id, - roleId: role.id, - }); + await oemMng.addRole(role); } await alertService.createAlert({ type: "info", - message: `유저 ${user.name}이(가) ${currentUser.name}에 의해 생성되었습니다.`, - companyId: user.companyId, + message: `고객사 ${oemMng.name}이(가) ${currentUser.name}에 의해 생성되었습니다.`, + companyId: oemMng.companyId, }); - return user; + return oemMng; } - async updateUser(id, updateData, currentUser) { - const { roleId, ...userFields } = updateData; + async updateOemMng(id, updateData, currentUser) { + const { roleId, ...oemMngFields } = updateData; - const user = await User.findByPk(id); - if (!user) throw new Error("User not found"); + const oemMng = await OemMng.findByPk(id); + if (!oemMng) throw new Error("OemMng not found"); // company_admin은 특정 필드를 수정할 수 없음 (예: role을 super_admin으로 변경 불가) if (currentUser.role === "company_admin") { @@ -139,13 +111,13 @@ class OemMngService { updateData.companyId = currentUser.companyId; } - const updatedUser = await user.update(userFields); + const updatedOemMng = await oemMng.update(oemMngFields); if (roleId) { const role = await Role.findOne({ where: { id: roleId, - companyId: user.companyId, + companyId: oemMng.companyId, }, }); @@ -153,33 +125,30 @@ class OemMngService { throw new Error("Role not found or does not belong to the company"); } - await UserRole.upsert({ - userId: user.id, - roleId: role.id, - }); + await oemMng.addRole(role); } await alertService.createAlert({ type: "info", - message: `유저 ${user.name}이(가) ${currentUser.name}에 의해 수정되었습니다.`, - companyId: user.companyId, + message: `고객사 ${oemMng.name}이(가) ${currentUser.name}에 의해 수정되었습니다.`, + companyId: oemMng.companyId, }); - return updatedUser; + return updatedOemMng; } - async deleteUser(id, currentUser) { - const user = await User.findByPk(id); - if (!user) throw new Error("User not found"); + async deleteOemMng(id, currentUser) { + const oemMng = await OemMng.findByPk(id); + if (!oemMng) throw new Error("OemMng not found"); - const userName = user.name; - const companyId = user.companyId; + const oemMngName = oemMng.name; + const companyId = oemMng.companyId; - await user.destroy(); + await oemMng.destroy(); await alertService.createAlert({ type: "info", - message: `유저 ${userName}이(가) ${currentUser.name}에 의해 삭제되었습니다.`, + message: `고객사 ${oemMngName}이(가) ${currentUser.name}에 의해 삭제되었습니다.`, companyId: companyId, }); @@ -187,4 +156,4 @@ class OemMngService { } } -module.exports = new UserService(); +module.exports = new OemMngService(); \ No newline at end of file diff --git a/plm-app/src/app/(admin)/common/oemmng/components/OemMngForm.tsx b/plm-app/src/app/(admin)/common/oemmng/components/OemMngForm.tsx index 1bf3365..49274b2 100644 --- a/plm-app/src/app/(admin)/common/oemmng/components/OemMngForm.tsx +++ b/plm-app/src/app/(admin)/common/oemmng/components/OemMngForm.tsx @@ -42,7 +42,7 @@ type FormSchema = z.infer; interface OemFormProps { initialData?: Partial; - onSubmit: (data: Partial) => void; + onSubmit: (data: Partial) => void; onCancel: () => void; } @@ -51,7 +51,7 @@ export const OemMngForm: React.FC = ({ onSubmit, onCancel, }) => { - const { token, user } = useAuthStore(); + //const { token, user } = useAuthStore(); const { toast } = useToast(); // Initialize the form with react-hook-form and zod resolver diff --git a/plm-app/src/app/(admin)/common/oemmng/page.tsx b/plm-app/src/app/(admin)/common/oemmng/page.tsx index 109f1ea..ce90677 100644 --- a/plm-app/src/app/(admin)/common/oemmng/page.tsx +++ b/plm-app/src/app/(admin)/common/oemmng/page.tsx @@ -23,20 +23,21 @@ import { OemMngForm } from "./components/OemMngForm"; import { Switch } from "@/components/ui/switch"; import { oemMng, PaginatedResponse } from "@/types/common/oemmng/oemmng"; -const AccountsPage = () => { +const OemMngPage = () => { const { token, user } = useAuthStore(); const [isOpen, setIsOpen] = React.useState(false); - const [editingUser, setEditingUser] = React.useState(null); + const [editingUser, setEditingUser] = React.useState(null); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); const { toast } = useToast(); const queryClient = useQueryClient(); - // Fetch users with pagination + // Fetch OEMs with pagination const { data, isLoading } = useQuery({ - queryKey: ["users", page, pageSize], + queryKey: ["oemMngs", page, pageSize], queryFn: async () => { - const { data } = await api.get("/api/v1/admin/users", { + const { data } = await api.get("/api/v1/app/oemmng/oemmngList", { + params: { page, limit: pageSize, @@ -44,15 +45,15 @@ const AccountsPage = () => { }); return { ...data, - users: data.users.map((user: User) => ({ - ...user, - Roles: user.Roles || [], + oemMngs: data.oemMngs.map((oemMngs: oemMng) => ({ + ...oemMngs, })), }; }, enabled: !!token, }); + const handlePageSizeChange = useCallback((newPageSize: number) => { setPageSize(newPageSize); setPage(1); @@ -60,21 +61,21 @@ const AccountsPage = () => { // Create user mutation const createMutation = useMutation({ - mutationFn: async (newUser: Partial) => { + mutationFn: async (newOem: Partial) => { // Include companyId in the user data - const userWithCompanyId = { - ...newUser, + const oemWithCompanyId = { + ...newOem, companyId: user?.companyId, }; - const { data } = await api.post( - "/api/v1/admin/users", - userWithCompanyId + const { data } = await api.post( + "/api/v1/app/oemmng/oemmngCreate", + oemWithCompanyId ); return data; }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["users"] }); + queryClient.invalidateQueries({ queryKey: ["oemMngs"] }); setIsOpen(false); toast({ title: "고객사 생성", @@ -92,15 +93,16 @@ const AccountsPage = () => { // Update user mutation const updateMutation = useMutation({ - mutationFn: async (userData: Partial) => { - const { data } = await api.put( - `/api/v1/admin/users/${userData.id}`, - userData + mutationFn: async (oemData: Partial) => { + const { data } = await api.put( + `/api/v1/app/oemmng/oemmngUpdate/${oemData.id}`, + + oemData ); return data; }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["users"] }); + queryClient.invalidateQueries({ queryKey: ["oemMngs"] }); setIsOpen(false); setEditingUser(null); toast({ @@ -120,10 +122,10 @@ const AccountsPage = () => { // Delete user mutation const deleteMutation = useMutation({ mutationFn: async (id: string) => { - await api.delete(`/api/v1/admin/users/${id}`); + await api.delete(`/api/v1/app/oemmng/oemmngDelete/${id}`); }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["users"] }); + queryClient.invalidateQueries({ queryKey: ["oemMngs"] }); toast({ title: "고객사 삭제", description: "고객사가 삭제되었습니다.", @@ -139,7 +141,7 @@ const AccountsPage = () => { }); // Table columns - const columns: ColumnDef[] = [ + const columns: ColumnDef[] = [ { id: "index", header: "No", @@ -151,13 +153,12 @@ const AccountsPage = () => { // header: "아이디", // }, { - accessorKey: "name", - header: "업체명/고객사사", + accessorKey: "oem_code", + header: "업체명/고객사", }, { - accessorKey: "Department.name", - header: "업체/고객사코드드", - cell: ({ row }) => row.original.Department?.name || "-", + accessorKey: "oem_name", + header: "OEM 이름", }, { accessorKey: "isActive", @@ -220,17 +221,17 @@ const AccountsPage = () => { - {/* Users Table */} + {/* OEMs Table */} 고객사 목록 - {data?.users && data.users.length > 0 ? ( + {data?.oemMngs && data.oemMngs.length > 0 ? ( <> { ); }; -export default AccountsPage; +export default OemMngPage; diff --git a/plm-app/src/types/common/oemmng/oemmng.ts b/plm-app/src/types/common/oemmng/oemmng.ts index 4eabd8b..4e18392 100644 --- a/plm-app/src/types/common/oemmng/oemmng.ts +++ b/plm-app/src/types/common/oemmng/oemmng.ts @@ -2,12 +2,14 @@ export interface oemMng { id: string; + index: number; oem_code?: string | null; oem_name?: string | null; writer?: string | null; regdate?: Date | null; status?: string | null; oem_no?: string | null; + isActive: boolean; } export interface oemMngResponse {