고객사 등록 완료

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

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.MaintenanceLog, { 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,
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;

View File

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

View File

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

View File

@ -42,7 +42,7 @@ type FormSchema = z.infer<typeof formSchema>;
interface OemFormProps {
initialData?: Partial<oemMng>;
onSubmit: (data: Partial<User>) => void;
onSubmit: (data: Partial<oemMng>) => void;
onCancel: () => void;
}
@ -51,7 +51,7 @@ export const OemMngForm: React.FC<OemFormProps> = ({
onSubmit,
onCancel,
}) => {
const { token, user } = useAuthStore();
//const { token, user } = useAuthStore();
const { toast } = useToast();
// 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 { 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<User | null>(null);
const [editingUser, setEditingUser] = React.useState<oemMng | null>(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<PaginatedResponse>({
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<User>) => {
mutationFn: async (newOem: Partial<oemMng>) => {
// Include companyId in the user data
const userWithCompanyId = {
...newUser,
const oemWithCompanyId = {
...newOem,
companyId: user?.companyId,
};
const { data } = await api.post<User>(
"/api/v1/admin/users",
userWithCompanyId
const { data } = await api.post<oemMng>(
"/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<User>) => {
const { data } = await api.put<User>(
`/api/v1/admin/users/${userData.id}`,
userData
mutationFn: async (oemData: Partial<oemMng>) => {
const { data } = await api.put<oemMng>(
`/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<User>[] = [
const columns: ColumnDef<oemMng>[] = [
{
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 = () => {
</Button>
</div>
{/* Users Table */}
{/* OEMs Table */}
<Card>
<CardHeader>
<CardTitle> </CardTitle>
</CardHeader>
<CardContent>
{data?.users && data.users.length > 0 ? (
{data?.oemMngs && data.oemMngs.length > 0 ? (
<>
<DataTable
columns={columns}
data={data.users}
data={data.oemMngs}
pagination={{
pageIndex: page - 1,
pageSize,
@ -289,4 +290,4 @@ const AccountsPage = () => {
);
};
export default AccountsPage;
export default OemMngPage;

View File

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