admin/userMng 전체폭 사용 + 부서관리 dept_info 스키마 풀 매핑
Build & Deploy to K8s / build-and-deploy (push) Successful in 4m27s
Build & Deploy to K8s / build-and-deploy (push) Successful in 4m27s
화면 폭: - 4개 페이지(사용자/권한 그룹/권한/부서) max-w 제거 → 화면 전체 사용 - 헤더 텍스트 컴팩트화 (text-xl + 작은 설명문) 부서관리 dept_info 스키마 1:1 매핑: - 누락 필드 6개 추가: master_sabun, master_user_id, location, location_name, data_type, sales_yn - frontend types/department.ts: Department / DepartmentFormData 확장 - frontend deptMngList: 폼 추가 + handleSelectDepartment / payload 매핑 - backend mapper: INSERT/UPDATE에 6개 컬럼 추가 - backend DepartmentService: insertParams/updateParams에 6개 필드 추가 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -92,6 +92,13 @@ public class DepartmentService extends BaseService {
|
||||
insertParams.put("show_in_chart", bodyParam(body, "show_in_chart", "show_in_chart"));
|
||||
insertParams.put("sort_order", bodyParam(body, "sort_order", "sort_order"));
|
||||
insertParams.put("status", bodyParam(body, "status", "status"));
|
||||
// dept_info 추가 필드 (master_*, location_*, data_type, sales_yn)
|
||||
insertParams.put("master_sabun", nullIfBlank(bodyParam(body, "master_sabun", "master_sabun")));
|
||||
insertParams.put("master_user_id", nullIfBlank(bodyParam(body, "master_user_id", "master_user_id")));
|
||||
insertParams.put("location", nullIfBlank(bodyParam(body, "location", "location")));
|
||||
insertParams.put("location_name", nullIfBlank(bodyParam(body, "location_name", "location_name")));
|
||||
insertParams.put("data_type", bodyParam(body, "data_type", "data_type"));
|
||||
insertParams.put("sales_yn", bodyParam(body, "sales_yn", "sales_yn"));
|
||||
sqlSession.insert("department.insertDepartment", insertParams);
|
||||
|
||||
log.info("부서 생성 성공: deptCode={}, deptName={}", deptCode, deptName);
|
||||
@@ -127,6 +134,13 @@ public class DepartmentService extends BaseService {
|
||||
params.put("show_in_chart", bodyParam(body, "show_in_chart", "show_in_chart"));
|
||||
params.put("sort_order", bodyParam(body, "sort_order", "sort_order"));
|
||||
params.put("status", bodyParam(body, "status", "status"));
|
||||
// dept_info 추가 필드
|
||||
params.put("master_sabun", nullIfBlank(bodyParam(body, "master_sabun", "master_sabun")));
|
||||
params.put("master_user_id", nullIfBlank(bodyParam(body, "master_user_id", "master_user_id")));
|
||||
params.put("location", nullIfBlank(bodyParam(body, "location", "location")));
|
||||
params.put("location_name", nullIfBlank(bodyParam(body, "location_name", "location_name")));
|
||||
params.put("data_type", bodyParam(body, "data_type", "data_type"));
|
||||
params.put("sales_yn", bodyParam(body, "sales_yn", "sales_yn"));
|
||||
|
||||
int updated = sqlSession.update("department.updateDepartment", params);
|
||||
if (updated == 0) {
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
WHERE DEPT_CODE ~ '^DEPT_[0-9]+$'
|
||||
</select>
|
||||
|
||||
<!-- 부서 생성 -->
|
||||
<!-- 부서 생성 (dept_info 스키마 1:1) -->
|
||||
<insert id="insertDepartment" parameterType="map">
|
||||
INSERT INTO DEPT_INFO (
|
||||
DEPT_CODE,
|
||||
@@ -108,6 +108,12 @@
|
||||
SHOW_IN_CHART,
|
||||
SORT_ORDER,
|
||||
STATUS,
|
||||
MASTER_SABUN,
|
||||
MASTER_USER_ID,
|
||||
LOCATION,
|
||||
LOCATION_NAME,
|
||||
DATA_TYPE,
|
||||
SALES_YN,
|
||||
CREATED_DATE
|
||||
) VALUES (
|
||||
#{dept_code},
|
||||
@@ -130,11 +136,17 @@
|
||||
COALESCE(#{show_in_chart}, 'Y'),
|
||||
COALESCE(#{sort_order}, 10),
|
||||
COALESCE(#{status}, 'active'),
|
||||
#{master_sabun},
|
||||
#{master_user_id},
|
||||
#{location},
|
||||
#{location_name},
|
||||
COALESCE(#{data_type}, 'real'),
|
||||
COALESCE(#{sales_yn}, 'N'),
|
||||
NOW()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- 부서 수정 -->
|
||||
<!-- 부서 수정 (dept_info 스키마 1:1) -->
|
||||
<update id="updateDepartment" parameterType="map">
|
||||
UPDATE DEPT_INFO
|
||||
SET
|
||||
@@ -154,7 +166,13 @@
|
||||
ERP_MANAGED = #{erp_managed},
|
||||
SHOW_IN_CHART = #{show_in_chart},
|
||||
SORT_ORDER = #{sort_order},
|
||||
STATUS = #{status}
|
||||
STATUS = #{status},
|
||||
MASTER_SABUN = #{master_sabun},
|
||||
MASTER_USER_ID = #{master_user_id},
|
||||
LOCATION = #{location},
|
||||
LOCATION_NAME = #{location_name},
|
||||
DATA_TYPE = #{data_type},
|
||||
SALES_YN = #{sales_yn}
|
||||
WHERE DEPT_CODE = #{dept_code}
|
||||
</update>
|
||||
|
||||
|
||||
@@ -33,20 +33,58 @@ import { getCompanyList } from "@/lib/api/company";
|
||||
import type { Department, DepartmentMember } from "@/types/department";
|
||||
import type { Company } from "@/types/company";
|
||||
|
||||
/**
|
||||
* dept_info 테이블 스키마에 1:1 매핑되는 Draft.
|
||||
*
|
||||
* 필드 매핑:
|
||||
* dept_code : 부서코드 (PK)
|
||||
* parent_dept_code : 상위부서코드
|
||||
* dept_name : 부서명
|
||||
* master_sabun : 부서장 사번
|
||||
* master_user_id : 부서장 사용자ID
|
||||
* location : 위치코드
|
||||
* location_name : 위치명
|
||||
* data_type : 데이터 구분 (정/임시)
|
||||
* status : 사용여부 (active/inactive)
|
||||
* sales_yn : 영업조직 여부 (Y/N)
|
||||
* company_name : 회사명 (조회용)
|
||||
* company_code : 회사코드
|
||||
* short_name : 부서약칭
|
||||
* dept_type : 부서유형 (dept|team|temp)
|
||||
* org_system : 조직체계
|
||||
* approval_manager : 결재 관리자
|
||||
* dept_manager : 부서 관리자
|
||||
* org_head : 조직장
|
||||
* zipcode : 우편번호
|
||||
* address1 : 주소1
|
||||
* address2 : 주소2
|
||||
* start_date : 시작일
|
||||
* end_date : 종료일
|
||||
* erp_managed : ERP 관리부서 (Y/N)
|
||||
* show_in_chart : 조직도 표시 (Y/N)
|
||||
* sort_order : 정렬순서
|
||||
*/
|
||||
interface DeptDetailDraft {
|
||||
dept_code: string;
|
||||
dept_name: string;
|
||||
parent_dept_code: string | null;
|
||||
dept_name: string;
|
||||
master_sabun: string;
|
||||
master_user_id: string;
|
||||
location: string;
|
||||
location_name: string;
|
||||
data_type: string;
|
||||
status: "active" | "inactive";
|
||||
sales_yn: "Y" | "N";
|
||||
company_code: string;
|
||||
short_name: string;
|
||||
dept_type: string;
|
||||
org_system: string;
|
||||
short_name: string;
|
||||
approval_manager: string;
|
||||
dept_manager: string;
|
||||
org_head: string;
|
||||
zipcode: string;
|
||||
address1: string;
|
||||
address2: string;
|
||||
status: "active" | "inactive";
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
erp_managed: "Y" | "N";
|
||||
@@ -56,18 +94,25 @@ interface DeptDetailDraft {
|
||||
|
||||
const emptyDraft = (companyCode = ""): DeptDetailDraft => ({
|
||||
dept_code: "",
|
||||
dept_name: "",
|
||||
parent_dept_code: null,
|
||||
dept_name: "",
|
||||
master_sabun: "",
|
||||
master_user_id: "",
|
||||
location: "",
|
||||
location_name: "",
|
||||
data_type: "real",
|
||||
status: "active",
|
||||
sales_yn: "N",
|
||||
company_code: companyCode,
|
||||
short_name: "",
|
||||
dept_type: "dept",
|
||||
org_system: "",
|
||||
short_name: "",
|
||||
approval_manager: "",
|
||||
dept_manager: "",
|
||||
org_head: "",
|
||||
zipcode: "",
|
||||
address1: "",
|
||||
address2: "",
|
||||
status: "active",
|
||||
start_date: new Date().toISOString().slice(0, 10),
|
||||
end_date: "",
|
||||
erp_managed: "Y",
|
||||
@@ -193,15 +238,23 @@ export default function DeptMngListPage() {
|
||||
});
|
||||
};
|
||||
|
||||
// ── 선택 시 상세정보 채우기 ───────────────────────────
|
||||
// ── 선택 시 상세정보 채우기 (dept_info 스키마 1:1) ────
|
||||
const handleSelectDepartment = (dept: Department) => {
|
||||
setSelectedCode(dept.dept_code);
|
||||
setIsNewMode(false);
|
||||
const d = dept as any;
|
||||
const loaded: DeptDetailDraft = {
|
||||
...emptyDraft(selectedCompanyCode),
|
||||
dept_code: dept.dept_code,
|
||||
dept_name: dept.dept_name,
|
||||
parent_dept_code: dept.parent_dept_code ?? null,
|
||||
dept_name: dept.dept_name,
|
||||
master_sabun: d.master_sabun ?? "",
|
||||
master_user_id: d.master_user_id ?? "",
|
||||
location: d.location ?? "",
|
||||
location_name: d.location_name ?? "",
|
||||
data_type: d.data_type ?? "real",
|
||||
sales_yn: ((d.sales_yn as "Y" | "N") ?? "N"),
|
||||
company_code: dept.company_code ?? selectedCompanyCode,
|
||||
short_name: dept.short_name ?? "",
|
||||
dept_type: dept.dept_type ?? "dept",
|
||||
org_system: dept.org_system ?? "",
|
||||
@@ -248,7 +301,7 @@ export default function DeptMngListPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 기본정보 탭 전체 필드를 payload 로 전달 — DepartmentFormData 와 1:1
|
||||
// 기본정보 탭 전체 필드를 payload 로 전달 — dept_info 스키마와 1:1
|
||||
const payload = {
|
||||
dept_name: draft.dept_name,
|
||||
parent_dept_code: draft.parent_dept_code,
|
||||
@@ -267,7 +320,14 @@ export default function DeptMngListPage() {
|
||||
show_in_chart: draft.show_in_chart,
|
||||
sort_order: draft.sort_order,
|
||||
status: draft.status,
|
||||
};
|
||||
// dept_info 추가 필드
|
||||
master_sabun: draft.master_sabun,
|
||||
master_user_id: draft.master_user_id,
|
||||
location: draft.location,
|
||||
location_name: draft.location_name,
|
||||
data_type: draft.data_type,
|
||||
sales_yn: draft.sales_yn,
|
||||
} as any;
|
||||
|
||||
try {
|
||||
if (isNewMode) {
|
||||
@@ -321,7 +381,7 @@ export default function DeptMngListPage() {
|
||||
// 렌더
|
||||
// ─────────────────────────────────────────────────────
|
||||
return (
|
||||
<div className="mx-auto flex h-full min-h-0 w-full max-w-[1700px] flex-col bg-background text-sm">
|
||||
<div className="flex h-full min-h-0 w-full flex-col bg-background text-sm">
|
||||
{/* 상단 타이틀 바 */}
|
||||
<div className="flex items-center justify-between border-b px-5 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -990,6 +1050,75 @@ function BasicInfoForm({
|
||||
className="h-8 text-sm"
|
||||
/>
|
||||
</Row>
|
||||
|
||||
<Row label="부서장 사번" hint>
|
||||
<Input
|
||||
value={draft.master_sabun}
|
||||
onChange={(e) => update("master_sabun", e.target.value)}
|
||||
className="h-8 text-sm"
|
||||
placeholder="부서장 사번"
|
||||
/>
|
||||
</Row>
|
||||
|
||||
<Row label="부서장 사용자ID">
|
||||
<Input
|
||||
value={draft.master_user_id}
|
||||
onChange={(e) => update("master_user_id", e.target.value)}
|
||||
className="h-8 text-sm"
|
||||
placeholder="user_id"
|
||||
/>
|
||||
</Row>
|
||||
|
||||
<Row label="위치코드">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Input
|
||||
value={draft.location}
|
||||
onChange={(e) => update("location", e.target.value)}
|
||||
className="h-8 text-sm"
|
||||
placeholder="위치코드"
|
||||
/>
|
||||
<Input
|
||||
value={draft.location_name}
|
||||
onChange={(e) => update("location_name", e.target.value)}
|
||||
className="h-8 text-sm"
|
||||
placeholder="위치명"
|
||||
/>
|
||||
</div>
|
||||
</Row>
|
||||
|
||||
<Row label="데이터구분" hint>
|
||||
<RadioGroup
|
||||
value={draft.data_type}
|
||||
onValueChange={(v) => update("data_type", v)}
|
||||
className="flex items-center gap-4"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<RadioGroupItem value="real" id="dtype-real" className="h-3.5 w-3.5" />
|
||||
<Label htmlFor="dtype-real" className="text-sm">정데이터</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<RadioGroupItem value="temp" id="dtype-temp" className="h-3.5 w-3.5" />
|
||||
<Label htmlFor="dtype-temp" className="text-sm">임시데이터</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</Row>
|
||||
|
||||
<Row label="영업조직 여부">
|
||||
<RadioGroup
|
||||
value={draft.sales_yn}
|
||||
onValueChange={(v) => update("sales_yn", v as "Y" | "N")}
|
||||
className="flex items-center gap-4"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<RadioGroupItem value="Y" id="sales-y" className="h-3.5 w-3.5" />
|
||||
<Label htmlFor="sales-y" className="text-sm">예</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<RadioGroupItem value="N" id="sales-n" className="h-3.5 w-3.5" />
|
||||
<Label htmlFor="sales-n" className="text-sm">아니오</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -557,7 +557,7 @@ export default function RolesPage() {
|
||||
if (!isAdmin) {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col bg-background">
|
||||
<div className="mx-auto w-full max-w-6xl space-y-4 p-6">
|
||||
<div className="w-full space-y-4 p-6">
|
||||
<div className="space-y-1 border-b pb-3">
|
||||
<h1 className="text-2xl font-bold tracking-tight">권한 관리</h1>
|
||||
</div>
|
||||
@@ -579,10 +579,9 @@ export default function RolesPage() {
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col bg-background">
|
||||
{/* 풀HD 기준 너무 크지 않도록 max-w 제한 */}
|
||||
<div className="mx-auto w-full max-w-[1600px] space-y-3 p-4 md:p-6">
|
||||
<div className="w-full space-y-3 p-6">
|
||||
<div className="space-y-1 border-b pb-3">
|
||||
<h1 className="text-2xl font-bold tracking-tight">권한 관리</h1>
|
||||
<h1 className="text-xl font-bold tracking-tight">권한 관리</h1>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
권한 그룹 선택 시 권한있는/없는 직원과 메뉴 권한이 로드되고, 체크 즉시 반영됩니다.
|
||||
</p>
|
||||
|
||||
@@ -171,7 +171,7 @@ export default function RolesPage() {
|
||||
if (!isAdmin) {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col bg-background">
|
||||
<div className="mx-auto w-full max-w-6xl space-y-4 p-6">
|
||||
<div className="w-full space-y-4 p-6">
|
||||
<div className="space-y-1 border-b pb-3">
|
||||
<h1 className="text-2xl font-bold tracking-tight">권한 그룹 관리</h1>
|
||||
<p className="text-xs text-muted-foreground">회사 내 권한 그룹을 생성하고 멤버를 관리합니다 (회사 관리자 이상)</p>
|
||||
@@ -196,12 +196,11 @@ export default function RolesPage() {
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col bg-background">
|
||||
{/* 풀HD 기준 너무 크지 않도록 max-w 제한 */}
|
||||
<div className="mx-auto w-full max-w-7xl space-y-4 p-4 md:p-6">
|
||||
<div className="w-full space-y-4 p-6">
|
||||
{/* 페이지 헤더 */}
|
||||
<div className="flex items-end justify-between border-b pb-3">
|
||||
<div className="space-y-0.5">
|
||||
<h1 className="text-2xl font-bold tracking-tight">권한 그룹 관리</h1>
|
||||
<h1 className="text-xl font-bold tracking-tight">권한 그룹 관리</h1>
|
||||
<p className="text-xs text-muted-foreground">회사 내 권한 그룹을 생성하고 멤버를 관리합니다 (회사 관리자 이상)</p>
|
||||
</div>
|
||||
<Button onClick={handleCreateRole} size="sm" className="gap-1.5 h-9">
|
||||
@@ -279,7 +278,7 @@ export default function RolesPage() {
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
|
||||
<div className="grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6">
|
||||
{filteredRoleGroups.map((role) => (
|
||||
<div
|
||||
key={role.objid}
|
||||
|
||||
@@ -110,11 +110,10 @@ export default function UserMngPage() {
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col bg-background">
|
||||
{/* 풀HD 기준 너무 크지 않도록 max-w 제한 */}
|
||||
<div className="mx-auto w-full max-w-7xl space-y-4 p-4 md:p-6">
|
||||
<div className="w-full space-y-4 p-6">
|
||||
{/* 페이지 헤더 */}
|
||||
<div className="space-y-1 border-b pb-3">
|
||||
<h1 className="text-2xl font-bold tracking-tight">사용자 관리</h1>
|
||||
<h1 className="text-xl font-bold tracking-tight">사용자 관리</h1>
|
||||
<p className="text-xs text-muted-foreground">시스템 사용자 계정 및 권한을 관리합니다</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,13 +2,21 @@
|
||||
* 부서 관리 관련 타입 정의
|
||||
*/
|
||||
|
||||
// 부서 정보 (dept_info 테이블 기반)
|
||||
// 부서 정보 (dept_info 테이블 1:1 매핑)
|
||||
export interface Department {
|
||||
dept_code: string; // 부서 코드
|
||||
dept_name: string; // 부서명
|
||||
company_code: string; // 회사 코드
|
||||
dept_code: string; // 부서 코드 (PK)
|
||||
parent_dept_code?: string | null; // 상위 부서 코드
|
||||
// dept_info 확장 컬럼
|
||||
dept_name: string; // 부서명
|
||||
master_sabun?: string | null; // 부서장 사번
|
||||
master_user_id?: string | null; // 부서장 사용자ID
|
||||
location?: string | null; // 위치코드
|
||||
location_name?: string | null; // 위치명
|
||||
created_date?: string | null; // 생성일시
|
||||
data_type?: string | null; // 데이터 구분 (real/temp)
|
||||
status?: "active" | "inactive" | null; // 사용여부
|
||||
sales_yn?: "Y" | "N" | null; // 영업조직 여부
|
||||
company_name?: string | null; // 회사명
|
||||
company_code: string; // 회사 코드
|
||||
short_name?: string | null; // 부서약칭
|
||||
dept_type?: string | null; // 부서유형 (dept/team/temp)
|
||||
org_system?: string | null; // 조직체계
|
||||
@@ -23,7 +31,6 @@ export interface Department {
|
||||
erp_managed?: "Y" | "N" | null;
|
||||
show_in_chart?: "Y" | "N" | null;
|
||||
sort_order?: number | null;
|
||||
status?: "active" | "inactive" | null;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
// UI용 추가 필드
|
||||
@@ -52,7 +59,7 @@ export interface UserDepartmentMapping {
|
||||
created_at?: string;
|
||||
}
|
||||
|
||||
// 부서 등록/수정 폼 데이터 — 기본정보 탭 모든 필드 전달 가능
|
||||
// 부서 등록/수정 폼 데이터 — dept_info 스키마 1:1
|
||||
export interface DepartmentFormData {
|
||||
dept_name: string; // 부서명 (필수)
|
||||
parent_dept_code?: string | null;
|
||||
@@ -71,6 +78,14 @@ export interface DepartmentFormData {
|
||||
show_in_chart?: "Y" | "N" | null;
|
||||
sort_order?: number | null;
|
||||
status?: "active" | "inactive" | null;
|
||||
// dept_info 추가 필드
|
||||
master_sabun?: string | null;
|
||||
master_user_id?: string | null;
|
||||
location?: string | null;
|
||||
location_name?: string | null;
|
||||
data_type?: string | null;
|
||||
sales_yn?: "Y" | "N" | null;
|
||||
dept_code?: string | null; // 일괄등록용 (자동 부여 시 미전달)
|
||||
}
|
||||
|
||||
// 부서 트리 노드 (UI용)
|
||||
|
||||
Reference in New Issue
Block a user