고객사 등록 완료

This commit is contained in:
chpark 2024-12-23 19:07:20 +09:00
parent 241c1f94e4
commit 5f778d9a2b
8 changed files with 207 additions and 107 deletions
plm-api/src
controllers/app/common
models
routes
services
plm-app/src
app/(admin)/common/oemmng
types/common/oemmng

View File

@ -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;

View File

@ -67,6 +67,7 @@ class Company extends Model {
this.hasMany(models.Equipment, { foreignKey: "companyId" }); this.hasMany(models.Equipment, { foreignKey: "companyId" });
this.hasMany(models.MaintenanceLog, { foreignKey: "companyId" }); this.hasMany(models.MaintenanceLog, { foreignKey: "companyId" });
this.hasMany(models.ApiKey, { foreignKey: "companyId" }); this.hasMany(models.ApiKey, { foreignKey: "companyId" });
this.hasMany(models.OemMng, { foreignKey: "companyId" });
} }
} }

View File

@ -27,14 +27,19 @@ class OemMng extends Model {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: true, allowNull: true,
}, },
status: { isActive: {
type: DataTypes.STRING(32), type: DataTypes.BOOLEAN,
allowNull: true, defaultValue: true,
}, },
oem_no: { oem_no: {
type: DataTypes.STRING(32), type: DataTypes.STRING(32),
allowNull: true, allowNull: true,
}, },
// 새로 추가할 필드들
companyId: {
type: DataTypes.UUID,
comment: "회사 ID",
},
}, },
{ {
sequelize, sequelize,
@ -50,6 +55,12 @@ class OemMng extends Model {
); );
return this; return this;
} }
static associate(models) {
// OemMng이 Company에 속함
this.belongsTo(models.Company, { foreignKey: "companyId" });
}
} }
module.exports = OemMng; module.exports = OemMng;

View File

@ -17,6 +17,7 @@ const healthController = require("../controllers/app/health/health.controller");
const companiesController = require("../controllers/admin/companies/companies.controller"); const companiesController = require("../controllers/admin/companies/companies.controller");
const deviceController = require("../controllers/app/device/device.controller"); const deviceController = require("../controllers/app/device/device.controller");
const commonController = require("../controllers/app/common/common.controller"); const commonController = require("../controllers/app/common/common.controller");
const oemMngController = require("../controllers/app/common/oemmng.controller");
router.use("/health", healthController); router.use("/health", healthController);
router.use("/auth", authController); router.use("/auth", authController);
@ -33,5 +34,6 @@ router.use("/department", departmentController);
router.use("/companies", companiesController); router.use("/companies", companiesController);
router.use("/devices", deviceController); router.use("/devices", deviceController);
router.use("/common", commonController); router.use("/common", commonController);
router.use("/oemmng", oemMngController);
module.exports = router; module.exports = router;

View File

@ -1,53 +1,36 @@
// src/services/oemmng.service.js
const { const {
User, OemMng,
Company, Company,
Branch,
Department,
UserRole,
Role, Role,
} = require("../models"); } = require("../models");
const { Op } = require("sequelize"); //const { Op } = require("sequelize");
const alertService = require("./alert.service"); const alertService = require("./alert.service");
class OemMngService { class OemMngService {
async findAll(currentUser, page = 1, limit = 10) { async findAll(currentUser, page = 1, limit = 10) {
try { try {
// Initialize the where clause // Initialize the where clause
let where = { let where = {};
role: { [Op.ne]: "super_admin" }, // Exclude super_admin users
};
// company_admin은 자신의 회사 유저만 조회 가능 // company_admin은 자신의 회사 유저만 조회 가능
if (currentUser.role === "company_admin") { if (currentUser.role === "company_admin") {
where.companyId = currentUser.companyId; where.companyId = currentUser.companyId;
} }
// isActive 필터 추가
//where.isActive = { [Op.eq]: true };
const offset = (page - 1) * limit; 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, where,
include: [ include: [
{ {
model: Company, model: Company,
attributes: ["id", "name"], attributes: ["id", "name"],
}, },
{
model: Branch,
attributes: ["id", "name"],
},
{
model: Department,
attributes: ["id", "name"],
},
{
model: Role,
through: { attributes: [] },
attributes: ["id", "name", "description"],
},
], ],
order: [["createdAt", "DESC"]], order: [["createdAt", "DESC"]],
offset, offset,
@ -56,17 +39,17 @@ class OemMngService {
}); });
// 인덱스 추가 // 인덱스 추가
const usersWithIndex = users.map((user, index) => { const oemMngsWithIndex = oemMngs.map((oem, index) => {
const userJson = user.toJSON(); const oemJson = oem.toJSON();
userJson.index = offset + index + 1; oemJson.index = offset + index + 1;
return userJson; return oemJson;
}); });
return { return {
total: count, total: count,
totalPages: Math.ceil(count / limit), totalPages: Math.ceil(count / limit),
currentPage: page, currentPage: page,
users: usersWithIndex, oemMngs: oemMngsWithIndex,
}; };
} catch (error) { } catch (error) {
console.error("Error in findAll:", error); console.error("Error in findAll:", error);
@ -75,34 +58,26 @@ class OemMngService {
} }
async findById(id) { async findById(id) {
return await User.findByPk(id, { return await OemMng.findByPk(id, {
include: [ include: [
{ {
model: Company, model: Company,
attributes: ["id", "name"], attributes: ["id", "name"],
}, },
{
model: Branch,
attributes: ["id", "name"],
},
{
model: Department,
attributes: ["id", "name"],
},
], ],
}); });
} }
async createUser(userData, currentUser) { async createOemMng(oemMngData, currentUser) {
const { roleId, ...userFields } = userData; const { roleId, ...oemMngFields } = oemMngData;
const user = await User.create(userFields); const oemMng = await OemMng.create(oemMngFields);
if (roleId) { if (roleId) {
const role = await Role.findOne({ const role = await Role.findOne({
where: { where: {
id: roleId, 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"); throw new Error("Role not found or does not belong to the company");
} }
await UserRole.create({ await oemMng.addRole(role);
userId: user.id,
roleId: role.id,
});
} }
await alertService.createAlert({ await alertService.createAlert({
type: "info", type: "info",
message: `유저 ${user.name}이(가) ${currentUser.name}에 의해 생성되었습니다.`, message: `고객사 ${oemMng.name}이(가) ${currentUser.name}에 의해 생성되었습니다.`,
companyId: user.companyId, companyId: oemMng.companyId,
}); });
return user; return oemMng;
} }
async updateUser(id, updateData, currentUser) { async updateOemMng(id, updateData, currentUser) {
const { roleId, ...userFields } = updateData; const { roleId, ...oemMngFields } = updateData;
const user = await User.findByPk(id); const oemMng = await OemMng.findByPk(id);
if (!user) throw new Error("User not found"); if (!oemMng) throw new Error("OemMng not found");
// company_admin은 특정 필드를 수정할 수 없음 (예: role을 super_admin으로 변경 불가) // company_admin은 특정 필드를 수정할 수 없음 (예: role을 super_admin으로 변경 불가)
if (currentUser.role === "company_admin") { if (currentUser.role === "company_admin") {
@ -139,13 +111,13 @@ class OemMngService {
updateData.companyId = currentUser.companyId; updateData.companyId = currentUser.companyId;
} }
const updatedUser = await user.update(userFields); const updatedOemMng = await oemMng.update(oemMngFields);
if (roleId) { if (roleId) {
const role = await Role.findOne({ const role = await Role.findOne({
where: { where: {
id: roleId, 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"); throw new Error("Role not found or does not belong to the company");
} }
await UserRole.upsert({ await oemMng.addRole(role);
userId: user.id,
roleId: role.id,
});
} }
await alertService.createAlert({ await alertService.createAlert({
type: "info", type: "info",
message: `유저 ${user.name}이(가) ${currentUser.name}에 의해 수정되었습니다.`, message: `고객사 ${oemMng.name}이(가) ${currentUser.name}에 의해 수정되었습니다.`,
companyId: user.companyId, companyId: oemMng.companyId,
}); });
return updatedUser; return updatedOemMng;
} }
async deleteUser(id, currentUser) { async deleteOemMng(id, currentUser) {
const user = await User.findByPk(id); const oemMng = await OemMng.findByPk(id);
if (!user) throw new Error("User not found"); if (!oemMng) throw new Error("OemMng not found");
const userName = user.name; const oemMngName = oemMng.name;
const companyId = user.companyId; const companyId = oemMng.companyId;
await user.destroy(); await oemMng.destroy();
await alertService.createAlert({ await alertService.createAlert({
type: "info", type: "info",
message: `유저 ${userName}이(가) ${currentUser.name}에 의해 삭제되었습니다.`, message: `고객사 ${oemMngName}이(가) ${currentUser.name}에 의해 삭제되었습니다.`,
companyId: companyId, companyId: companyId,
}); });
@ -187,4 +156,4 @@ class OemMngService {
} }
} }
module.exports = new UserService(); module.exports = new OemMngService();

View File

@ -42,7 +42,7 @@ type FormSchema = z.infer<typeof formSchema>;
interface OemFormProps { interface OemFormProps {
initialData?: Partial<oemMng>; initialData?: Partial<oemMng>;
onSubmit: (data: Partial<User>) => void; onSubmit: (data: Partial<oemMng>) => void;
onCancel: () => void; onCancel: () => void;
} }
@ -51,7 +51,7 @@ export const OemMngForm: React.FC<OemFormProps> = ({
onSubmit, onSubmit,
onCancel, onCancel,
}) => { }) => {
const { token, user } = useAuthStore(); //const { token, user } = useAuthStore();
const { toast } = useToast(); const { toast } = useToast();
// Initialize the form with react-hook-form and zod resolver // Initialize the form with react-hook-form and zod resolver

View File

@ -23,20 +23,21 @@ import { OemMngForm } from "./components/OemMngForm";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { oemMng, PaginatedResponse } from "@/types/common/oemmng/oemmng"; import { oemMng, PaginatedResponse } from "@/types/common/oemmng/oemmng";
const AccountsPage = () => { const OemMngPage = () => {
const { token, user } = useAuthStore(); const { token, user } = useAuthStore();
const [isOpen, setIsOpen] = React.useState(false); const [isOpen, setIsOpen] = React.useState(false);
const [editingUser, setEditingUser] = React.useState<User | null>(null); const [editingUser, setEditingUser] = React.useState<oemMng | null>(null);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10); const [pageSize, setPageSize] = useState(10);
const { toast } = useToast(); const { toast } = useToast();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
// Fetch users with pagination // Fetch OEMs with pagination
const { data, isLoading } = useQuery<PaginatedResponse>({ const { data, isLoading } = useQuery<PaginatedResponse>({
queryKey: ["users", page, pageSize], queryKey: ["oemMngs", page, pageSize],
queryFn: async () => { queryFn: async () => {
const { data } = await api.get("/api/v1/admin/users", { const { data } = await api.get("/api/v1/app/oemmng/oemmngList", {
params: { params: {
page, page,
limit: pageSize, limit: pageSize,
@ -44,15 +45,15 @@ const AccountsPage = () => {
}); });
return { return {
...data, ...data,
users: data.users.map((user: User) => ({ oemMngs: data.oemMngs.map((oemMngs: oemMng) => ({
...user, ...oemMngs,
Roles: user.Roles || [],
})), })),
}; };
}, },
enabled: !!token, enabled: !!token,
}); });
const handlePageSizeChange = useCallback((newPageSize: number) => { const handlePageSizeChange = useCallback((newPageSize: number) => {
setPageSize(newPageSize); setPageSize(newPageSize);
setPage(1); setPage(1);
@ -60,21 +61,21 @@ const AccountsPage = () => {
// Create user mutation // Create user mutation
const createMutation = useMutation({ const createMutation = useMutation({
mutationFn: async (newUser: Partial<User>) => { mutationFn: async (newOem: Partial<oemMng>) => {
// Include companyId in the user data // Include companyId in the user data
const userWithCompanyId = { const oemWithCompanyId = {
...newUser, ...newOem,
companyId: user?.companyId, companyId: user?.companyId,
}; };
const { data } = await api.post<User>( const { data } = await api.post<oemMng>(
"/api/v1/admin/users", "/api/v1/app/oemmng/oemmngCreate",
userWithCompanyId oemWithCompanyId
); );
return data; return data;
}, },
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["users"] }); queryClient.invalidateQueries({ queryKey: ["oemMngs"] });
setIsOpen(false); setIsOpen(false);
toast({ toast({
title: "고객사 생성", title: "고객사 생성",
@ -92,15 +93,16 @@ const AccountsPage = () => {
// Update user mutation // Update user mutation
const updateMutation = useMutation({ const updateMutation = useMutation({
mutationFn: async (userData: Partial<User>) => { mutationFn: async (oemData: Partial<oemMng>) => {
const { data } = await api.put<User>( const { data } = await api.put<oemMng>(
`/api/v1/admin/users/${userData.id}`, `/api/v1/app/oemmng/oemmngUpdate/${oemData.id}`,
userData
oemData
); );
return data; return data;
}, },
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["users"] }); queryClient.invalidateQueries({ queryKey: ["oemMngs"] });
setIsOpen(false); setIsOpen(false);
setEditingUser(null); setEditingUser(null);
toast({ toast({
@ -120,10 +122,10 @@ const AccountsPage = () => {
// Delete user mutation // Delete user mutation
const deleteMutation = useMutation({ const deleteMutation = useMutation({
mutationFn: async (id: string) => { mutationFn: async (id: string) => {
await api.delete(`/api/v1/admin/users/${id}`); await api.delete(`/api/v1/app/oemmng/oemmngDelete/${id}`);
}, },
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["users"] }); queryClient.invalidateQueries({ queryKey: ["oemMngs"] });
toast({ toast({
title: "고객사 삭제", title: "고객사 삭제",
description: "고객사가 삭제되었습니다.", description: "고객사가 삭제되었습니다.",
@ -139,7 +141,7 @@ const AccountsPage = () => {
}); });
// Table columns // Table columns
const columns: ColumnDef<User>[] = [ const columns: ColumnDef<oemMng>[] = [
{ {
id: "index", id: "index",
header: "No", header: "No",
@ -151,13 +153,12 @@ const AccountsPage = () => {
// header: "아이디", // header: "아이디",
// }, // },
{ {
accessorKey: "name", accessorKey: "oem_code",
header: "업체명/고객사", header: "업체명/고객사",
}, },
{ {
accessorKey: "Department.name", accessorKey: "oem_name",
header: "업체/고객사코드드", header: "OEM 이름",
cell: ({ row }) => row.original.Department?.name || "-",
}, },
{ {
accessorKey: "isActive", accessorKey: "isActive",
@ -220,17 +221,17 @@ const AccountsPage = () => {
</Button> </Button>
</div> </div>
{/* Users Table */} {/* OEMs Table */}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle> </CardTitle> <CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{data?.users && data.users.length > 0 ? ( {data?.oemMngs && data.oemMngs.length > 0 ? (
<> <>
<DataTable <DataTable
columns={columns} columns={columns}
data={data.users} data={data.oemMngs}
pagination={{ pagination={{
pageIndex: page - 1, pageIndex: page - 1,
pageSize, pageSize,
@ -289,4 +290,4 @@ const AccountsPage = () => {
); );
}; };
export default AccountsPage; export default OemMngPage;

View File

@ -2,12 +2,14 @@
export interface oemMng { export interface oemMng {
id: string; id: string;
index: number;
oem_code?: string | null; oem_code?: string | null;
oem_name?: string | null; oem_name?: string | null;
writer?: string | null; writer?: string | null;
regdate?: Date | null; regdate?: Date | null;
status?: string | null; status?: string | null;
oem_no?: string | null; oem_no?: string | null;
isActive: boolean;
} }
export interface oemMngResponse { export interface oemMngResponse {