ITN Dev 07-23
feat: 기관에 문정원 담당자·담당 변호사 연락처 컬럼 추가
신청기관 담당자 1종만 저장하던 기관 테이블에 문정원 담당자(부서/담당자/연락처/이메일)와
담당 변호사(성명/연락처/이메일/배정일) 정보를 추가해, 채널관리 화면에서 세 담당자를
한 번에 저장하고 다른 화면(기관관리·업무메모)에서도 쓸 수 있게 한다.

- V3 마이그레이션으로 organization에 컬럼 8개 추가 (upsertBySeed는 그대로 둬서
  엑셀 재시드가 새 컬럼을 건드리지 않는다)
- ContactRequest/OrgResponse/updateContact에 8개 필드를 선택값으로 반영
- 컨트롤러 테스트 2건(신규 필드 왕복, 신규 필드 없는 기존 페이로드) +
  매퍼 테스트 2건(영속화 왕복, 재시드 시 보존) 추가
@435af9eb1359a76b32bf915798f0cc00c0444c83
src/main/java/kr/itn/itnhub/org/ContactRequest.java
--- src/main/java/kr/itn/itnhub/org/ContactRequest.java
+++ src/main/java/kr/itn/itnhub/org/ContactRequest.java
@@ -2,11 +2,20 @@
 
 import jakarta.validation.constraints.Email;
 import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
 
 public record ContactRequest(
         @NotBlank String deptName,
         @NotBlank String managerName,
         String managerTitle,
         @NotBlank String managerPhone,
-        @NotBlank @Email String managerEmail) {
+        @NotBlank @Email String managerEmail,
+        @Size(max = 200) String mjDeptName,
+        @Size(max = 100) String mjManagerName,
+        @Size(max = 50) String mjManagerPhone,
+        @Size(max = 200) String mjManagerEmail,
+        @Size(max = 100) String lawyerName,
+        @Size(max = 50) String lawyerPhone,
+        @Size(max = 200) String lawyerEmail,
+        @Size(max = 20) String lawyerAssignedDate) {
 }
src/main/java/kr/itn/itnhub/org/OrgResponse.java
--- src/main/java/kr/itn/itnhub/org/OrgResponse.java
+++ src/main/java/kr/itn/itnhub/org/OrgResponse.java
@@ -10,6 +10,14 @@
         String managerTitle,
         String managerPhone,
         String managerEmail,
+        String mjDeptName,
+        String mjManagerName,
+        String mjManagerPhone,
+        String mjManagerEmail,
+        String lawyerName,
+        String lawyerPhone,
+        String lawyerEmail,
+        String lawyerAssignedDate,
         String channelIdMj,
         String channelIdLaw) {
 
@@ -24,6 +32,14 @@
                 org.getManagerTitle(),
                 org.getManagerPhone(),
                 org.getManagerEmail(),
+                org.getMjDeptName(),
+                org.getMjManagerName(),
+                org.getMjManagerPhone(),
+                org.getMjManagerEmail(),
+                org.getLawyerName(),
+                org.getLawyerPhone(),
+                org.getLawyerEmail(),
+                org.getLawyerAssignedDate(),
                 org.getChannelIdMj(),
                 org.getChannelIdLaw());
     }
src/main/java/kr/itn/itnhub/org/Organization.java
--- src/main/java/kr/itn/itnhub/org/Organization.java
+++ src/main/java/kr/itn/itnhub/org/Organization.java
@@ -11,6 +11,14 @@
     private String managerTitle;
     private String managerPhone;
     private String managerEmail;
+    private String mjDeptName;
+    private String mjManagerName;
+    private String mjManagerPhone;
+    private String mjManagerEmail;
+    private String lawyerName;
+    private String lawyerPhone;
+    private String lawyerEmail;
+    private String lawyerAssignedDate;
     private String channelIdMj;
     private String channelIdLaw;
 
@@ -70,6 +78,30 @@
     public String getManagerEmail() { return managerEmail; }
     public void setManagerEmail(String managerEmail) { this.managerEmail = managerEmail; }
 
+    public String getMjDeptName() { return mjDeptName; }
+    public void setMjDeptName(String mjDeptName) { this.mjDeptName = mjDeptName; }
+
+    public String getMjManagerName() { return mjManagerName; }
+    public void setMjManagerName(String mjManagerName) { this.mjManagerName = mjManagerName; }
+
+    public String getMjManagerPhone() { return mjManagerPhone; }
+    public void setMjManagerPhone(String mjManagerPhone) { this.mjManagerPhone = mjManagerPhone; }
+
+    public String getMjManagerEmail() { return mjManagerEmail; }
+    public void setMjManagerEmail(String mjManagerEmail) { this.mjManagerEmail = mjManagerEmail; }
+
+    public String getLawyerName() { return lawyerName; }
+    public void setLawyerName(String lawyerName) { this.lawyerName = lawyerName; }
+
+    public String getLawyerPhone() { return lawyerPhone; }
+    public void setLawyerPhone(String lawyerPhone) { this.lawyerPhone = lawyerPhone; }
+
+    public String getLawyerEmail() { return lawyerEmail; }
+    public void setLawyerEmail(String lawyerEmail) { this.lawyerEmail = lawyerEmail; }
+
+    public String getLawyerAssignedDate() { return lawyerAssignedDate; }
+    public void setLawyerAssignedDate(String lawyerAssignedDate) { this.lawyerAssignedDate = lawyerAssignedDate; }
+
     public String getChannelIdMj() { return channelIdMj; }
     public void setChannelIdMj(String channelIdMj) { this.channelIdMj = channelIdMj; }
 
src/main/java/kr/itn/itnhub/org/OrganizationService.java
--- src/main/java/kr/itn/itnhub/org/OrganizationService.java
+++ src/main/java/kr/itn/itnhub/org/OrganizationService.java
@@ -29,6 +29,14 @@
         org.setManagerTitle(request.managerTitle());
         org.setManagerPhone(request.managerPhone());
         org.setManagerEmail(request.managerEmail());
+        org.setMjDeptName(request.mjDeptName());
+        org.setMjManagerName(request.mjManagerName());
+        org.setMjManagerPhone(request.mjManagerPhone());
+        org.setMjManagerEmail(request.mjManagerEmail());
+        org.setLawyerName(request.lawyerName());
+        org.setLawyerPhone(request.lawyerPhone());
+        org.setLawyerEmail(request.lawyerEmail());
+        org.setLawyerAssignedDate(request.lawyerAssignedDate());
         mapper.updateContact(org);
 
         return OrgResponse.of(mapper.findById(id));
 
src/main/resources/db/migration/V3__org_contacts.sql (added)
+++ src/main/resources/db/migration/V3__org_contacts.sql
@@ -0,0 +1,9 @@
+alter table organization
+    add column mj_dept_name         varchar(200),
+    add column mj_manager_name      varchar(100),
+    add column mj_manager_phone     varchar(50),
+    add column mj_manager_email     varchar(200),
+    add column lawyer_name          varchar(100),
+    add column lawyer_phone         varchar(50),
+    add column lawyer_email         varchar(200),
+    add column lawyer_assigned_date varchar(20);
src/main/resources/mapper/OrganizationMapper.xml
--- src/main/resources/mapper/OrganizationMapper.xml
+++ src/main/resources/mapper/OrganizationMapper.xml
@@ -6,6 +6,8 @@
   <sql id="columns">
     id, org_no, org_name, channel_slug,
     dept_name, manager_name, manager_title, manager_phone, manager_email,
+    mj_dept_name, mj_manager_name, mj_manager_phone, mj_manager_email,
+    lawyer_name, lawyer_phone, lawyer_email, lawyer_assigned_date,
     channel_id_mj, channel_id_law
   </sql>
 
@@ -45,12 +47,20 @@
 
   <update id="updateContact" parameterType="kr.itn.itnhub.org.Organization">
     update organization set
-      dept_name     = #{deptName},
-      manager_name  = #{managerName},
-      manager_title = #{managerTitle},
-      manager_phone = #{managerPhone},
-      manager_email = #{managerEmail},
-      updated_at    = now()
+      dept_name            = #{deptName},
+      manager_name         = #{managerName},
+      manager_title        = #{managerTitle},
+      manager_phone        = #{managerPhone},
+      manager_email        = #{managerEmail},
+      mj_dept_name         = #{mjDeptName},
+      mj_manager_name      = #{mjManagerName},
+      mj_manager_phone     = #{mjManagerPhone},
+      mj_manager_email     = #{mjManagerEmail},
+      lawyer_name          = #{lawyerName},
+      lawyer_phone         = #{lawyerPhone},
+      lawyer_email         = #{lawyerEmail},
+      lawyer_assigned_date = #{lawyerAssignedDate},
+      updated_at           = now()
     where id = #{id}
   </update>
 
src/test/java/kr/itn/itnhub/org/OrganizationControllerTest.java
--- src/test/java/kr/itn/itnhub/org/OrganizationControllerTest.java
+++ src/test/java/kr/itn/itnhub/org/OrganizationControllerTest.java
@@ -17,6 +17,7 @@
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.nullValue;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
@@ -88,6 +89,64 @@
     }
 
     @Test
+    void 문정원_담당자와_변호사_정보를_포함해_저장하면_응답과_재조회에_모두_반영된다() throws Exception {
+        mvc.perform(put("/api/orgs/{id}/contact", orgId)
+                        .with(csrf())
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content("""
+                                {
+                                  "deptName": "데이터정보화팀",
+                                  "managerName": "송민지",
+                                  "managerTitle": "과장",
+                                  "managerPhone": "02-3475-5434",
+                                  "managerEmail": "ming@arirang.com",
+                                  "mjDeptName": "문화체육관광부 저작권정책과",
+                                  "mjManagerName": "김문정",
+                                  "mjManagerPhone": "02-1234-5678",
+                                  "mjManagerEmail": "mj@mcst.go.kr",
+                                  "lawyerName": "이변호",
+                                  "lawyerPhone": "02-9876-5432",
+                                  "lawyerEmail": "lawyer@lawfirm.kr",
+                                  "lawyerAssignedDate": "2026-07-21"
+                                }
+                                """))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.mjDeptName").value("문화체육관광부 저작권정책과"))
+                .andExpect(jsonPath("$.mjManagerName").value("김문정"))
+                .andExpect(jsonPath("$.mjManagerPhone").value("02-1234-5678"))
+                .andExpect(jsonPath("$.mjManagerEmail").value("mj@mcst.go.kr"))
+                .andExpect(jsonPath("$.lawyerName").value("이변호"))
+                .andExpect(jsonPath("$.lawyerPhone").value("02-9876-5432"))
+                .andExpect(jsonPath("$.lawyerEmail").value("lawyer@lawfirm.kr"))
+                .andExpect(jsonPath("$.lawyerAssignedDate").value("2026-07-21"));
+
+        mvc.perform(get("/api/orgs"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$[0].mjManagerName").value("김문정"))
+                .andExpect(jsonPath("$[0].lawyerName").value("이변호"));
+    }
+
+    @Test
+    void 문정원_변호사_정보없이_기존방식_페이로드만으로도_저장에_성공한다() throws Exception {
+        mvc.perform(put("/api/orgs/{id}/contact", orgId)
+                        .with(csrf())
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content("""
+                                {
+                                  "deptName": "데이터정보화팀",
+                                  "managerName": "송민지",
+                                  "managerTitle": "과장",
+                                  "managerPhone": "02-3475-5434",
+                                  "managerEmail": "ming@arirang.com"
+                                }
+                                """))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.status").value("READY"))
+                .andExpect(jsonPath("$.mjManagerName").value(nullValue()))
+                .andExpect(jsonPath("$.lawyerName").value(nullValue()));
+    }
+
+    @Test
     void 존재하지_않는_기관ID로_담당자정보를_수정하면_404다() throws Exception {
         long missingId = orgId + 999999L;
 
src/test/java/kr/itn/itnhub/org/OrganizationMapperTest.java
--- src/test/java/kr/itn/itnhub/org/OrganizationMapperTest.java
+++ src/test/java/kr/itn/itnhub/org/OrganizationMapperTest.java
@@ -114,6 +114,47 @@
     }
 
     @Test
+    void 문정원_담당자와_변호사_정보가_저장되고_그대로_읽힌다() {
+        mapper.upsertBySeed(seedRow("003", "한국문학번역원", "정보관리팀"));
+        Organization org = mapper.findAll().get(0);
+
+        org.setMjDeptName("문화체육관광부 저작권정책과");
+        org.setMjManagerName("김문정");
+        org.setMjManagerPhone("02-1234-5678");
+        org.setMjManagerEmail("mj@mcst.go.kr");
+        org.setLawyerName("이변호");
+        org.setLawyerPhone("02-9876-5432");
+        org.setLawyerEmail("lawyer@lawfirm.kr");
+        org.setLawyerAssignedDate("2026-07-21");
+        mapper.updateContact(org);
+
+        Organization after = mapper.findById(org.getId());
+        assertThat(after.getMjDeptName()).isEqualTo("문화체육관광부 저작권정책과");
+        assertThat(after.getMjManagerName()).isEqualTo("김문정");
+        assertThat(after.getMjManagerPhone()).isEqualTo("02-1234-5678");
+        assertThat(after.getMjManagerEmail()).isEqualTo("mj@mcst.go.kr");
+        assertThat(after.getLawyerName()).isEqualTo("이변호");
+        assertThat(after.getLawyerPhone()).isEqualTo("02-9876-5432");
+        assertThat(after.getLawyerEmail()).isEqualTo("lawyer@lawfirm.kr");
+        assertThat(after.getLawyerAssignedDate()).isEqualTo("2026-07-21");
+    }
+
+    @Test
+    void 시드_재실행은_문정원_변호사_정보를_지우지_않는다() {
+        mapper.upsertBySeed(seedRow("001", "국제방송교류재단", "데이터정보화팀"));
+        Organization org = mapper.findAll().get(0);
+        org.setMjManagerName("김문정");
+        org.setLawyerName("이변호");
+        mapper.updateContact(org);
+
+        mapper.upsertBySeed(seedRow("001", "국제방송교류재단", "데이터정보화팀"));
+
+        Organization after = mapper.findById(org.getId());
+        assertThat(after.getMjManagerName()).isEqualTo("김문정");
+        assertThat(after.getLawyerName()).isEqualTo("이변호");
+    }
+
+    @Test
     void 목록은_연번_오름차순이다() {
         mapper.upsertBySeed(seedRow("010", "국립아시아문화전당", "기획운영과"));
         mapper.upsertBySeed(seedRow("002", "세종학당재단", "콘텐츠개발팀"));
Add a comment
List